Introducing 'bind'
Steps towards creating our own 'let!'
Last updated
Was this helpful?
Steps towards creating our own 'let!'
Last updated
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.
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:
And here's the Bind
method documentation, along with a real example:
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:
the compiler converts it to calls to Bind
, like this:
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.
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:
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:
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:
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.
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:
And here is the implementation of Option.bind
:
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
:
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:
Next, using our own version of "bind" (a.k.a. "pipeInto")
Next, using a computation expression:
And finally, using bind as an infix operation:
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.
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:
and then create your own computation expression builder class so that you can use it in a workflow, as shown below.
Part 2 -- create a bind function
Once you have the first part working, extend the idea by adding two more functions:
And then with these functions, you should be able to write code like this:
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 ).