Much Ado About Monads – Creating Extended Builders Part II

In this series, we’ve looked custom computation expressions, what they are, how they are applicable to development and how we might implement them in F#.  In the previous post, we went over some of the basic methods you can include on your custom computation expression to allow for a more rich programmatic model than the linear style provided via both Bind and Return.

Explaining More Methods

Let’s continue where we left off last time.  Previously, we covered Bind, Return, ReturnFrom, Zero and Combine.  This time, we’ll pick it up with exception handling and resource management.

Implementing the TryCatch Method

The first method we’ll cover today is called TryCatch, which is self describing in what it does.  This allows us, when implemented, to use try/catch blocks inside our custom computation expression.  In order to enable this functionality, the method should have the following signature which takes a computation and a function exception handler which takes a function and returns a Monad<’T> result.

type Builder =
  member TryWith : comp : Monad<'T> * 
                   handler : (exn -> Monad<'T>) -> 
                   Monad<'T>

Now, let’s write a test to show an example of how this would work.  Keeping this test as minimalistic as possible, we simply will throw an exception in the try block and then in the catch, set a flag to indicate it was caught.  In addition to the below test, we should have a test as well which shows that if no exception happens, then the compensation block should not be called.

[<Fact>]
let ``TryCatch should catch exception``() =
  let called = ref false
  let r = reader {
    try
      failwith "FAIL"
    with e ->
      called := true }
  
  runReader r ()

  isTrue !called

Implementing this method is quite straight forward.  We should return a Reader constructed from a function which takes an environment, try to run our reader with our environment, and should an exception occur, run our handler with the exception and our environment.

type ReaderBuilder() =
  member this.TryWith(computation, handler) =
    Reader (fun env ->
      try
        runReader computation env
      with e -> runReader (handler e) env)

Next, we turn our attention to the counterpart to this function which allows for the use of a finally block.

Implementing the TryFinally Method

In standard F#, we have two mechanisms for dealing with exceptions and that’s with either a try/catch block or try/finally block.  Unfortunately, the language, as of now, does not support the try/catch/finally mechanism, so ultimately you must choose which one is more applicable to your situation.  Dealing with situations in which something must happen no matter whether it succeeded or failed is important, even in computation expressions.  Given that requirement, we have the ability to enable this feature by implementing the TryFinally Method.  In order to do so, we must implement the function with the following signature:

type Builder =
  member TryFinally : computation : Monad<'T> * 
                      compensation : (unit -> unit) -> 
                      Monad<'T>

We can write a simple test to show how this might work.  Like above, we can have a method that throws an exception and in our finally block, we ensure that it has indeed been called, but as well, have a test which shows that a method that does not throw an exception should also execute the finally block.  For now, let’s just show the former test.

[<Fact>]
let ``TryFinally with exception should execute finally``() =
  let called = ref false
  let r = reader {
    try
      failwith "FAIL"
    finally
      called := true }
  
  runReader r ()

  isTrue !called

Implementing this method is quite similar to the implementation of TryCatch in that instead of having a catch block, we have a finally.

type ReaderBuilder() =
  member this.TryFinally(computation, compensation) =
    Reader (fun env ->
      try
        runReader computation env
      finally
        compensation())

Now that we have some basic exception handling features, what about the automatic disposal of resources with using blocks?

Implementing the Using Method

Outside of computation expressions, we have two ways of handling automatic resource management, either through the using function or through it’s syntactic sugar use keyword equivalent.  In computation expressions, we have the same capabilities if we implement the Using method.  In order to enable this feature, the method signature should be the following:

type Builder =
  member Using : resource : #IDisposable * 
                 binder   : (#IDisposable -> Monad<'T>) -> 
                 Monad<'T>

This function takes two arguments, the resource which must implement System.IDisposable, and a binder which represents the rest of the computation.  We can write a test to verify this behavior by creating a simple IDisposable instance and ensure that the Dispose method was called such as the following:

[<Fact>]
let ``Using should call Dispose``() =
  let disposed = ref false
  let disposable = 
    { new IDisposable with
        member __.Dispose() = disposed := true }

  let r = reader {
    use d = disposable
    () }

  runReader r ()

  isTrue !disposed

One feature that I really enjoy in F# is object expressions which allow us to easily create IDisposable objects on the fly, so creating stubs is easy for smaller operations such as this.  I’m then able to ensure that my Dispose method was called at the end of my computation expression r.  We can implement this method by using the TryFinally method we’ve already created.

type ReaderBuilder() =

  member this.Using(resource:#IDisposable, body) =
    this.TryFinally(body res, 
      (fun () -> match res with null -> () | disp -> disp.Dispose()))

In our implementation, we simply called the TryFinally method with our compensation block calling Dispose if our resource is not null.  That’s great, but what about the case where our resource happens to be wrapped in the monadic type?  Fortunately for us, we have the use! keyword which allows us to call Bind then Using with our resource to ensure that it is disposed properly.  We can verify this behavior as well with a test.

[<Fact>]
let ``use! should call Dispose``() =
  let disposed = ref false
  let disposable = reader {
    return { new IDisposable with
               member __.Dispose() = disposed := true } }

  let r = reader {
    use! d = disposable
    () }

  runReader r ()

  isTrue !disposed

To make our disposable object a reader, we simply wrap it in a reader block and return our IDisposable instance.  Now inside of our Reader r, we can use the use! keyword to ensure that not only do we bind to the IDisposable object, but ensure that it is disposed as well. 

Conclusion

That’s enough for now, but we have a bit more in store including:

  • For
  • While
  • Yield/YieldFrom

Besides the usual Bind and Return functions that we’ve talked about extensively here, there are quite a few other constructs which allow for rich programming models inside our computation expressions.  Whereas LINQ has a rather linear programming model based upon monads, the computation expression includes richer features which include exception handling, control statements, loops, and side effects. 

We have a bit more in this series as well when talking about Monads/Computation Expressions as we work our way towards the Continuation Monad and the basis for the Async Workflow and the Reactive Extensions.

219 Comments

Comments have been disabled for this content.