Sugar is a small monadic library that provides syntactic sugar to help you manipulate error aware expressions.
module Strict : sig ... end
The first thing you'll notice if you look at the code examples using Sugar is that monads are used everywhere. They are not hidden with compiler extensions, nor look like anything other than monads.
But threre's a few conventions to style your code. These conventions are not mandatory - they just make it easier to undertand what is cool (and whats not) when writing monadic code in OCaml.
For example, a code like this:
return [1; 2; 3]
>>= fun list ->
return (List.length list)
>>= fun _len ->
do_something ()
>>= fun () ->
return "hello world"
Could be refactored to look like this:
( List.length <$> return [1; 2; 3] )
>>=
( fun _len ->
do_something ()
)
>>lazy
( return "Hello World" )
unit result
. If you want to ignore any value without writing an anonymous function, you can use >>>lazy.
Sugar is easy to use and customize. At the beginning, you create a result monad specific to your project. For that, you describe things like the errors of your project and if an underlining monad should be used for async. After that, you can basically ignore the fact that your result monad was customized at all. You get the same clean interface for all error aware computations.
It's interesting to say that describing your errors at an early development state is a good practice. The correct usage of this monad is enforced by Sugar interfaces. The most basic ones are:
bind
combinator. It resolves the expression in the left, and if successful, applies the returned value to the function in the right. If case of failures, it just returns the error. These constructs abstract away things like the usage of async code and the concrete result type, making code easier to read.
It would be great if exceptions would not be used as openly as they are in libraries like the Stdlib and Lwt. But they are, and you might want to use then nonetheless. You can still have some control over exceptions while using Sugar.
Sugar has some *strict* modules builders that take exception handling into account before creating a result monad. That is reflected in small changes in the Error
and Monad
modules provided as dependencies.
When you use a strict result monad, Sugar will handle the try-with
blocks for you and ask your Error
module to decide if the detected exception should be converted into one of your project errors, or some other action should be taken, like logging the error and kiling the process.
One common problem in the transition for writing heavily monadic code is that you need to think about how to handle errors in an expressive way.
Here's a code fragment that relies merely on exception and try-with
blocks to handle errors.
try
delete_user "john"
with
Not_found -> ()
And here is the same example, now using error aware expressions and a result monad to handle failures:
delete_user "john"
>---------
( function
| Not_found -> return ()
| e -> throw e
)
As you can see, both versions are easy to write and read, but the latter is much safer, since it does not hide the fact that some errors can still be returned to the caller.
If you use an async library with Sugar, chances are you will need to convert monads back and fourth to access functionality outside your project. Fear not, you will have access to the underlining monad with the operator >>>=.
The code bellow mixes both usage of the underlining bind, the result bind, and exception handling. Take into account that the code raising the exception just exemplifies an interaction with Lwt code that already raises exceptions. You should avoid writing this kind of code.
module MyError = struct
type t = Unexpected of exn
let panic e = Unexpected e
end
module MyResult = Sugar.Strict.Promise.Make (MyError) (Lwt)
open MyResult
open MyResult.Infix
open MyError
let get_message () =
Lwt_unix.file_exists "README.md"{{: string } text }
>>>=
( fun exists ->
if exists then
return "file exists"
else
Lwt.fail (Failure "file does not exists")
)
>---------
( function
Unexpected e -> return ("Unexpected: " ^ (Printexc.to_string e))
)