F# for Fun and Profit
  • Introduction
  • Getting started
    • Contents of the book
    • "Why use F#?" in one page
    • Installing and using F#
    • F# syntax in 60 seconds
    • Learning F#
    • Troubleshooting F#
    • Low-risk ways to use F# at work
      • Twenty six low-risk ways to use F# at work
      • Using F# for development and devops scripts
      • Using F# for testing
      • Using F# for database related tasks
      • Other interesting ways of using F# at work
  • Why use F#?
    • The "Why use F#?" Series
      • Introduction to the 'Why use F#' series
      • F# syntax in 60 seconds
      • Comparing F# with C#: A simple sum
      • Comparing F# with C#: Sorting
      • Comparing F# with C#: Downloading a web page
      • Four Key Concepts
      • Conciseness
      • Type inference
      • Low overhead type definitions
      • Using functions to extract boilerplate code
      • Using functions as building blocks
      • Pattern matching for conciseness
      • Convenience
      • Out-of-the-box behavior for types
      • Functions as interfaces
      • Partial Application
      • Active patterns
      • Correctness
      • Immutability
      • Exhaustive pattern matching
      • Using the type system to ensure correct code
      • Worked example: Designing for correctness
      • Concurrency
      • Asynchronous programming
      • Messages and Agents
      • Functional Reactive Programming
      • Completeness
      • Seamless interoperation with .NET libraries
      • Anything C# can do...
      • Why use F#: Conclusion
  • Thinking Functionally
    • The "Thinking Functionally" Series
      • Thinking Functionally: Introduction
      • Mathematical functions
      • Function Values and Simple Values
      • How types work with functions
      • Currying
      • Partial application
      • Function associativity and composition
      • Defining functions
      • Function signatures
      • Organizing functions
      • Attaching functions to types
      • Worked example: A stack based calculator
  • Understanding F# ###
    • The "Expressions and syntax" Series
      • Expressions and syntax: Introduction
      • Expressions vs. statements
      • Overview of F# expressions
      • Binding with let, use, and do
      • F# syntax: indentation and verbosity
      • Parameter and value naming conventions
      • Control flow expressions
      • Exceptions
      • Match expressions
      • Formatted text using printf
      • Worked example: Parsing command line arguments
      • Worked example: Roman numerals
    • The "Understanding F# types" Series
      • Understanding F# types: Introduction
      • Overview of types in F#
      • Type abbreviations
      • Tuples
      • Records
      • Discriminated Unions
      • The Option type
      • Enum types
      • Built-in .NET types
      • Units of measure
      • Understanding type inference
    • Choosing between collection functions
    • The "Object-oriented programming in F#" Series
      • Object-oriented programming in F#: Introduction
      • Classes
      • Inheritance and abstract classes
      • Interfaces
      • Object expressions
    • The "Computation Expressions" Series
      • Computation expressions: Introduction
      • Understanding continuations
      • Introducing 'bind'
      • Computation expressions and wrapper types
      • More on wrapper types
      • Implementing a builder: Zero and Yield
      • Implementing a builder: Combine
      • Implementing a builder: Delay and Run
      • Implementing a builder: Overloading
      • Implementing a builder: Adding laziness
      • Implementing a builder: The rest of the standard methods
    • Organizing modules in a project
    • The "Dependency cycles" Series
      • Cyclic dependencies are evil
      • Refactoring to remove cyclic dependencies
      • Cycles and modularity in the wild
    • The "Porting from C#" Series
      • Porting from C# to F#: Introduction
      • Getting started with direct porting
  • Functional Design ###
    • The "Designing with types" Series
      • Designing with types: Introduction
      • Single case union types
      • Making illegal states unrepresentable
      • Discovering new concepts
      • Making state explicit
      • Constrained strings
      • Non-string types
      • Designing with types: Conclusion
    • Algebraic type sizes and domain modelling
    • Thirteen ways of looking at a turtle
      • Thirteen ways of looking at a turtle (part 2)
      • Thirteen ways of looking at a turtle - addendum
  • Functional Patterns ###
    • How to design and code a complete program
    • A functional approach to error handling (Railway oriented programming)
      • Railway oriented programming: Carbonated edition
    • The "Understanding monoids" Series
      • Monoids without tears
      • Monoids in practice
      • Working with non-monoids
    • The "Understanding Parser Combinators" Series
      • Understanding Parser Combinators
      • Building a useful set of parser combinators
      • Improving the parser library
      • Writing a JSON parser from scratch
    • The "Handling State" Series
      • Dr Frankenfunctor and the Monadster
      • Completing the body of the Monadster
      • Refactoring the Monadster
    • The "Map and Bind and Apply, Oh my!" Series
      • Understanding map and apply
      • Understanding bind
      • Using the core functions in practice
      • Understanding traverse and sequence
      • Using map, apply, bind and sequence in practice
      • Reinventing the Reader monad
      • Map and Bind and Apply, a summary
    • The "Recursive types and folds" Series
      • Introduction to recursive types
      • Catamorphism examples
      • Introducing Folds
      • Understanding Folds
      • Generic recursive types
      • Trees in the real world
    • The "A functional approach to authorization" Series
      • A functional approach to authorization
      • Constraining capabilities based on identity and role
      • Using types as access tokens
  • Testing
    • An introduction to property-based testing
    • Choosing properties for property-based testing
  • Examples and Walkthroughs
    • Worked example: Designing for correctness
    • Worked example: A stack based calculator
    • Worked example: Parsing command line arguments
    • Worked example: Roman numerals
    • Commentary on 'Roman Numerals Kata with Commentary'
    • Calculator Walkthrough: Part 1
      • Calculator Walkthrough: Part 2
      • Calculator Walkthrough: Part 3
      • Calculator Walkthrough: Part 4
    • Enterprise Tic-Tac-Toe
      • Enterprise Tic-Tac-Toe, part 2
    • Writing a JSON parser from scratch
  • Other
    • Ten reasons not to use a statically typed functional programming language
    • Why I won't be writing a monad tutorial
    • Is your programming language unreasonable?
    • We don't need no stinking UML diagrams
    • Introvert and extrovert programming languages
    • Swapping type-safety for high performance using compiler directives
Powered by GitBook
On this page
  • Implementing "While"
  • "While" in use
  • Handling exceptions with "try..with"
  • Implementing "try..finally"
  • Implementing "using"
  • "For" revisited

Was this helpful?

  1. Understanding F# ###
  2. The "Computation Expressions" Series

Implementing a builder: The rest of the standard methods

Implementing While, Using, and exception handling

PreviousImplementing a builder: Adding lazinessNextOrganizing modules in a project

Last updated 5 years ago

Was this helpful?

We're coming into the home stretch now. There are only a few more builder methods that need to be covered, and then you will be ready to tackle anything!

These methods are:

  • While for repetition.

  • TryWith and TryFinally for handling exceptions.

  • Use for managing disposables

Remember, as always, that not all methods need to be implemented. If While is not relevant to you, don't bother with it.

One important note before we get started: all the methods discussed here rely on being used. If you are not using delay functions, then none of the methods will give the expected results.

Implementing "While"

We all know what "while" means in normal code, but what does it mean in the context of a computation expression? To understand, we have to revisit the concept of continuations again.

In previous posts, we saw that a series of expressions is converted into a chain of continuations like this:

Bind(1,fun x -> 
   Bind(2,fun y -> 
     Bind(x + y,fun z -> 
        Return(z)  // or Yield

And this is the key to understanding a "while" loop -- it can be expanded in the same way.

First, some terminology. A while loop has two parts:

  • There is a test at the top of the "while" loop which is evaluated each time to determine whether the body should be run. When it evaluates to false, the while loop is "exited". In computation expressions, the test part is known as the "guard".

    The test function has no parameters, and returns a bool, so its signature is unit -> bool, of course.

  • And there is the body of the "while" loop, evaluated each time until the "while" test fails. In computation expressions, this is a delay function that evaluates to a wrapped value. Since the body of the while loop is always the same, the same function is evaluated each time.

    The body function has no parameters, and returns nothing, and so its signature is just unit -> wrapped unit.

With this in place, we can create pseudo-code for a while loop using continuations:

// evaluate test function
let bool = guard()  
if not bool 
then
    // exit loop
    return what??
else
    // evaluate the body function
    body()         

    // back to the top of the while loop 

    // evaluate test function again
    let bool' = guard()  
    if not bool' 
    then
        // exit loop
        return what??
    else 
        // evaluate the body function again
        body()         

        // back to the top of the while loop

        // evaluate test function a third time
        let bool'' = guard()  
        if not bool'' 
        then
            // exit loop
            return what??
        else
            // evaluate the body function a third time
            body()         

            // etc

One question that is immediately apparent is: what should be returned when the while loop test fails? Well, we have seen this before with if..then.., and the answer is of course to use the Zero value.

The next thing is that the body() result is being discarded. Yes, it is a unit function, so there is no value to return, but even so, in our expressions, we want to be able to hook into this so we can add behavior behind the scenes. And of course, this calls for using the Bind function.

So here is a revised version of the pseudo-code, using Zero and Bind:

// evaluate test function
let bool = guard()  
if not bool 
then
    // exit loop
    return Zero
else
    // evaluate the body function
    Bind( body(), fun () ->  

        // evaluate test function again
        let bool' = guard()  
        if not bool' 
        then
            // exit loop
            return Zero
        else 
            // evaluate the body function again
            Bind( body(), fun () ->  

                // evaluate test function a third time
                let bool'' = guard()  
                if not bool'' 
                then
                    // exit loop
                    return Zero
                else
                    // evaluate the body function again
                    Bind( body(), fun () ->  

                    // etc

In this case, the continuation function passed into Bind has a unit parameter, because the body function does not have a value.

Finally, the pseudo-code can be simplified by collapsing it into a recursive function like this:

member this.While(guard, body) =
    // evaluate test function
    if not (guard()) 
    then 
        // exit loop
        this.Zero() 
    else
        // evaluate the body function 
        this.Bind( body(), fun () -> 
            // call recursively
            this.While(guard, body))

And indeed, this is the standard "boiler-plate" implementation for While in almost all builder classes.

It is a subtle but important point that the value of Zero must be chosen properly. In previous posts, we saw that we could set the value for Zero to be None or Some () depending on the workflow. For While to work however, the Zero must be set to Some () and not None, because passing None into Bind will cause the whole thing to aborted early.

Also note that, although this is a recursive function, we didn't need the rec keyword. It is only needed for standalone functions that are recursive, not methods.

"While" in use

Let's look at it being used in the trace builder. Here's the complete builder class, with the While method:

type TraceBuilder() =
    member this.Bind(m, f) = 
        match m with 
        | None -> 
            printfn "Binding with None. Exiting."
        | Some a -> 
            printfn "Binding with Some(%A). Continuing" a
        Option.bind f m

    member this.Return(x) = 
        Some x

    member this.ReturnFrom(x) = 
        x

    member this.Zero() = 
        printfn "Zero"
        this.Return ()

    member this.Delay(f) = 
        printfn "Delay"
        f

    member this.Run(f) = 
        f()

    member this.While(guard, body) =
        printfn "While: test"
        if not (guard()) 
        then 
            printfn "While: zero"
            this.Zero() 
        else
            printfn "While: body"
            this.Bind( body(), fun () -> 
                this.While(guard, body))  

// make an instance of the workflow                
let trace = new TraceBuilder()

If you look at the signature for While, you will see that the body parameter is unit -> unit option, that is, a delayed function. As noted above, if you don't implement Delay properly, you will get unexpected behavior and cryptic compiler errors.

type TraceBuilder =
    // other members
    member
      While : guard:(unit -> bool) * body:(unit -> unit option) -> unit option

And here is a simple loop using a mutable value that is incremented each time round.

let mutable i = 1
let test() = i < 5
let inc() = i <- i + 1

let m = trace { 
    while test() do
        printfn "i is %i" i
        inc() 
    }

Handling exceptions with "try..with"

Exception handling is implemented in a similar way.

If we look at a try..with expression for example, it has two parts:

  • There is the body of the "try", evaluated once. In a computation expressions, this will be a delayed function that evaluates to a wrapped value. The body function has no parameters, and so its signature is just unit -> wrapped type.

  • The "with" part handles the exception. It has an exception as a parameters, and returns the same type as the "try" part, so its signature is exception -> wrapped type.

With this in place, we can create pseudo-code for the exception handler:

try
    let wrapped = delayedBody()  
    wrapped  // return a wrapped value
with
| e -> handlerPart e

And this maps exactly to a standard implementation:

member this.TryWith(body, handler) =
    try 
        printfn "TryWith Body"
        this.ReturnFrom(body())
    with 
        e ->
            printfn "TryWith Exception handling"
            handler e

As you can see, it is common to use pass the returned value through ReturnFrom so that it gets the same treatment as other wrapped values.

Here is an example snippet to test how the handling works:

trace { 
    try
        failwith "bang"
    with
    | e -> printfn "Exception! %s" e.Message
    } |> printfn "Result %A"

Implementing "try..finally"

try..finally is very similar to try..with.

  • There is the body of the "try", evaluated once. The body function has no parameters, and so its signature is unit -> wrapped type.

  • The "finally" part is always called. It has no parameters, and returns a unit, so its signature is unit -> unit.

Just as with try..with, the standard implementation is obvious.

member this.TryFinally(body, compensation) =
    try 
        printfn "TryFinally Body"
        this.ReturnFrom(body())
    finally 
        printfn "TryFinally compensation"
        compensation()

Another little snippet:

trace { 
    try
        failwith "bang"
    finally
        printfn "ok" 
    } |> printfn "Result %A"

Implementing "using"

The final method to implement is Using. This is the builder method for implementing the use! keyword.

This is what the MSDN documentation says about use!:

{| use! value = expr in cexpr |}

is translated to:

builder.Bind(expr, (fun value -> builder.Using(value, (fun value -> {| cexpr |} ))))

In other words, the use! keyword triggers both a Bind and a Using. First a Bind is done to unpack the wrapped value, and then the unwrapped disposable is passed into Using to ensure disposal, with the continuation function as the second parameter.

Implementing this is straightforward. Similar to the other methods, we have a body, or continuation part, of the "using" expression, which is evaluated once. This body function has a "disposable" parameter, and so its signature is #IDisposable -> wrapped type.

Of course we want to ensure that the disposable value is always disposed no matter what, so we need to wrap the call to the body function in a TryFinally.

Here's a standard implementation:

member this.Using(disposable:#System.IDisposable, body) =
    let body' = fun () -> body disposable
    this.TryFinally(body', fun () -> 
        match disposable with 
            | null -> () 
            | disp -> disp.Dispose())

Notes:

  • The parameter to TryFinally is a unit -> wrapped, with a unit as the first parameter, so we created a delayed version of the body that is passed in.

  • Disposable is a class, so it could be null, and we have to handle that case specially. Otherwise we just dispose it in the "finally" continuation.

Here's a demonstration of Using in action. Note that the makeResource makes a wrapped disposable. If it wasn't wrapped, we wouldn't need the special use! and could just use a normal use instead.

let makeResource name =
    Some { 
    new System.IDisposable with
    member this.Dispose() = printfn "Disposing %s" name
    }

trace { 
    use! x = makeResource "hello"
    printfn "Disposable in use"
    return 1
    } |> printfn "Result: %A"

"For" revisited

Finally, we can revisit how For is implemented. In the previous examples, For took a simple list parameter. But with Using and While under our belts, we can change it to accept any IEnumerable<_> or sequence.

Here's the standard implementation for For now:

member this.For(sequence:seq<_>, body) =
       this.Using(sequence.GetEnumerator(),fun enum -> 
            this.While(enum.MoveNext, 
                this.Delay(fun () -> body enum.Current)))
 {% endhighlight fsharp %}

As you can see, it is quite different from the previous implementation, in order to handle a generic `IEnumerable<_>`.

* We explicitly iterate using an `IEnumerator<_>`.
* `IEnumerator<_>` implements `IDisposable`, so we wrap the enumerator in a `Using`.
* We use `While .. MoveNext` to iterate.
* Next, we pass the `enum.Current` into the body function
* Finally, we delay the call to the body function using `Delay`

## Complete code without tracing 

Up to now, all the builder methods have been made more complex than necessary by the adding of tracing and printing expressions. The tracing is helpful to understand what is going on,
but it can obscure the simplicity of the methods.

So as a final step, let's have a look at the complete code for the "trace" builder class, but this time without any extraneous code at all.  Even though the code is cryptic, the purpose and implementation of each method should now be familiar to you.

```fsharp
type TraceBuilder() =

    member this.Bind(m, f) = 
        Option.bind f m

    member this.Return(x) = Some x

    member this.ReturnFrom(x) = x

    member this.Yield(x) = Some x

    member this.YieldFrom(x) = x

    member this.Zero() = this.Return ()

    member this.Delay(f) = f

    member this.Run(f) = f()

    member this.While(guard, body) =
        if not (guard()) 
        then this.Zero() 
        else this.Bind( body(), fun () -> 
            this.While(guard, body))  

    member this.TryWith(body, handler) =
        try this.ReturnFrom(body())
        with e -> handler e

    member this.TryFinally(body, compensation) =
        try this.ReturnFrom(body())
        finally compensation() 

    member this.Using(disposable:#System.IDisposable, body) =
        let body' = fun () -> body disposable
        this.TryFinally(body', fun () -> 
            match disposable with 
                | null -> () 
                | disp -> disp.Dispose())

    member this.For(sequence:seq<_>, body) =
        this.Using(sequence.GetEnumerator(),fun enum -> 
            this.While(enum.MoveNext, 
                this.Delay(fun () -> body enum.Current)))

After all this discussion, the code seems quite tiny now. And yet this builder implements every standard method, uses delayed functions. A lot of functionality in a just a few lines!

delays