Module Sugar

Sugar is a small monadic library that provides syntactic sugar to help you manipulate error aware expressions.

Submodules

module S : sig ... end

Sugar module signatures.

module Promise = Sugar__.Promise_builder

Use to create a result monad on top of an arbitrary monad.

module Result = Sugar__.Result_builder

Create a result monad for synchronous processing.

module Option : sig ... end

An implementation of Result interface for the option type.

module Strict : sig ... end

Introduction

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"  )
  

Your own result monad

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:

​ These constructs abstract away things like the usage of async code and the concrete result type, making code easier to read.

Exception awareness

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.

Expressive error handling

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.

Interacting with threading libraries

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))
    )