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
  • Introducing "Bind "
  • A standalone bind function
  • Option.bind and the "maybe" workflow revisited
  • Reviewing the different approaches so far ##
  • Exercise: How well do you understand?
  • Summary ##

Was this helpful?

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

Introducing 'bind'

Steps towards creating our own 'let!'

PreviousUnderstanding continuationsNextComputation expressions and wrapper types

Last updated 5 years ago

Was this helpful?

In the last post we talked about how we can think of let as a nice syntax for doing continuations behind scenes. And we introduced a pipeInto function that allowed us to add hooks into the continuation pipeline.

Now we are ready to look at our first builder method, Bind, which formalizes this approach and is the core of any computation expression.

Introducing "Bind "

The describes the let! expression as syntactic sugar for a Bind method. Let's look at this again:

Here's the let! expression documentation, along with a real example:

// documentation
{| let! pattern = expr in cexpr |}

// real example
let! x = 43 in some expression

And here's the Bind method documentation, along with a real example:

// documentation
builder.Bind(expr, (fun pattern -> {| cexpr |}))

// real example
builder.Bind(43, (fun x -> some expression))

Notice a few interesting things about this:

  • Bind takes two parameters, an expression (43) and a lambda.

  • The parameter of the lambda (x) is bound to the expression passed in as the first parameter. (In this case at least. More on this later.)

  • The parameters of Bind are reversed from the order they are in let!.

So in other words, if we chain a number of let! expressions together like this:

let! x = 1
let! y = 2
let! z = x + y

the compiler converts it to calls to Bind, like this:

Bind(1, fun x ->
Bind(2, fun y ->
Bind(x + y, fun z ->
etc

I think you can see where we are going with this by now.

Indeed, our pipeInto function is exactly the same as the Bind method.

This is a key insight: computation expressions are just a way to create nice syntax for something that we could do ourselves.

A standalone bind function

Having a "bind" function like this is actually a standard functional pattern, and it is not dependent on computation expressions at all.

So when you think of bind this this way, you can see that it is similar to piping or composition.

In fact, you can turn it into an infix operation like this:

let (>>=) m f = pipeInto(m,f)

By the way, this symbol ">>=" is the standard way of writing bind as an infix operator. If you ever see it used in other F# code, that is probably what it represents.

Going back to the safe divide example, we can now write the workflow on one line, like this:

let divideByWorkflow x y w z = 
    x |> divideBy y >>= divideBy w >>= divideBy z

You might be wondering exactly how this is different from normal piping or composition? It's not immediately obvious.

The answer is twofold:

  • First, the bind function has extra customized behavior for each situation. It is not a generic function, like pipe or composition.

  • Second, the input type of the value parameter (m above) is not necessarily the same as the output type of the function parameter (f above), and so one of the things that bind does is handle this mismatch elegantly so that functions can be chained.

As we will see in the next post, bind generally works with some "wrapper" type. The value parameter might be of WrapperType<TypeA>, and then the signature of the function parameter of bind function is always TypeA -> WrapperType<TypeB>.

In the particular case of the bind for safe divide, the wrapper type is Option. The type of the value parameter (m above) is Option<int> and the signature of the function parameter (f above) is int -> Option<int>.

To see bind used in a different context, here is an example of the logging workflow expressed using a infix bind function:

let (>>=) m f = 
    printfn "expression is %A" m
    f m

let loggingWorkflow = 
    1 >>= (+) 2 >>= (*) 42 >>= id

In this case, there is no wrapper type. Everything is an int. But even so, bind has the special behavior that performs the logging behind the scenes.

Option.bind and the "maybe" workflow revisited

In the F# libraries, you will see Bind functions or methods in many places. Now you know what they are for!

A particularly useful one is Option.bind, which does exactly what we wrote by hand above, namely

  • If the input parameter is None, then don't call the continuation function.

  • If the input parameter is Some, then do call the continuation function, passing in the contents of the Some.

Here was our hand-crafted function:

let pipeInto (m,f) =
   match m with
   | None -> 
       None
   | Some x -> 
       x |> f

And here is the implementation of Option.bind:

module Option = 
    let bind f m =
       match m with
       | None -> 
           None
       | Some x -> 
           x |> f

There is a moral in this -- don't be too hasty to write your own functions. There may well be library functions that you can reuse.

Here is the "maybe" workflow, rewritten to use Option.bind:

type MaybeBuilder() =
    member this.Bind(m, f) = Option.bind f m
    member this.Return(x) = Some x

Reviewing the different approaches so far ##

We've used four different approaches for the "safe divide" example so far. Let's put them together side by side and compare them once more.

Note: I have renamed the original pipeInto function to bind, and used Option.bind instead of our original custom implementation.

First the original version, using an explicit workflow:

module DivideByExplicit = 

    let divideBy bottom top =
        if bottom = 0
        then None
        else Some(top/bottom)

    let divideByWorkflow x y w z = 
        let a = x |> divideBy y 
        match a with
        | None -> None  // give up
        | Some a' ->    // keep going
            let b = a' |> divideBy w
            match b with
            | None -> None  // give up
            | Some b' ->    // keep going
                let c = b' |> divideBy z
                match c with
                | None -> None  // give up
                | Some c' ->    // keep going
                    //return 
                    Some c'
    // test
    let good = divideByWorkflow 12 3 2 1
    let bad = divideByWorkflow 12 3 0 1

Next, using our own version of "bind" (a.k.a. "pipeInto")

module DivideByWithBindFunction = 

    let divideBy bottom top =
        if bottom = 0
        then None
        else Some(top/bottom)

    let bind (m,f) =
        Option.bind f m

    let return' x = Some x

    let divideByWorkflow x y w z = 
        bind (x |> divideBy y, fun a ->
        bind (a |> divideBy w, fun b ->
        bind (b |> divideBy z, fun c ->
        return' c 
        )))

    // test
    let good = divideByWorkflow 12 3 2 1
    let bad = divideByWorkflow 12 3 0 1

Next, using a computation expression:

module DivideByWithCompExpr = 

    let divideBy bottom top =
        if bottom = 0
        then None
        else Some(top/bottom)

    type MaybeBuilder() =
        member this.Bind(m, f) = Option.bind f m
        member this.Return(x) = Some x

    let maybe = new MaybeBuilder()

    let divideByWorkflow x y w z = 
        maybe 
            {
            let! a = x |> divideBy y 
            let! b = a |> divideBy w
            let! c = b |> divideBy z
            return c
            }    

    // test
    let good = divideByWorkflow 12 3 2 1
    let bad = divideByWorkflow 12 3 0 1

And finally, using bind as an infix operation:

module DivideByWithBindOperator = 

    let divideBy bottom top =
        if bottom = 0
        then None
        else Some(top/bottom)

    let (>>=) m f = Option.bind f m

    let divideByWorkflow x y w z = 
        x |> divideBy y 
        >>= divideBy w 
        >>= divideBy z 

    // test
    let good = divideByWorkflow 12 3 2 1
    let bad = divideByWorkflow 12 3 0 1

Bind functions turn out to be very powerful. In the next post we'll see that combining bind with wrapper types creates an elegant way of passing extra information around in the background.

Exercise: How well do you understand?

Before you move on to the next post, why don't you test yourself to see if you have understood everything so far?

Here is a little exercise for you.

Part 1 - create a workflow

First, create a function that parses a string into a int:

let strToInt str = ???

and then create your own computation expression builder class so that you can use it in a workflow, as shown below.

let stringAddWorkflow x y z = 
    yourWorkflow 
        {
        let! a = strToInt x
        let! b = strToInt y
        let! c = strToInt z
        return a + b + c
        }    

// test
let good = stringAddWorkflow "12" "3" "2"
let bad = stringAddWorkflow "12" "xyz" "2"

Part 2 -- create a bind function

Once you have the first part working, extend the idea by adding two more functions:

let strAdd str i = ???
let (>>=) m f = ???

And then with these functions, you should be able to write code like this:

let good = strToInt "1" >>= strAdd "2" >>= strAdd "3"
let bad = strToInt "1" >>= strAdd "xyz" >>= strAdd "3"

Summary ##

Here's a summary of the points covered in this post:

  • Computation expressions provide a nice syntax for continuation passing, hiding the chaining logic for us.

  • bind is the key function that links the output of one step to the input of the next step.

  • The symbol >>= is the standard way of writing bind as an infix operator.

First, why is it called "bind"? Well, as we've seen, a "bind" function or method can be thought of as feeding an input value to a function. This is known as "" a value to the parameter of the function (recall that all functions have only ).

MSDN page on computation expressions
binding
one parameter