In the previous post, I started a first F# implementation, showing "beauty" of F# code ;). Now it is time to improve a bit the controller function to be more fluent.
Introduce the bind
function
As spotted in the previous post, we could do something with the recurring pattern around Success/Failure pattern matching in the following code:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
We could define a function with two parameters
- the function to call in case of Success on the embedded value
- the previous Result (Success or Failure)
It returns a new Result (Success or Failure).
1: 2: 3: 4: |
|
With this function, we can rewrite the addProductToCartController
:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
It reduces the indentation, but it not really more readable for now. Note toHttpResponse
function pushes at the end the decision of transforming the last Result to an HttpResponseMessage, that's quite nice. For example:
1: 2: 3: 4: |
|
There is still this weird bind
call in the middle of our code. In functional language, we like custom operators, but in fact they are quite "standard"...and there is one for bind
: >>=
. So let's define it in F#:
1:
|
|
A bit confusing? It uses composition (>>), let's explain step by step:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
|
Then, our controller can be rewritten:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
It removes the bind word, but it requires more brackets :/ (and for some readers it adds cryptic/not usual functional operators...).
Introduce Monad by example
So let's try to talk a bit of theory around Monad from this example (please tell me if I make mistakes in my explanation, I am far from an expert).
The bind
function transforms a "world-crossing function" between a "normal world" and an "elevated world" (see Scott Wlaschin explanation) into a function that manipulate only "elevated world" values. For example, it can transform a function with singature a -> Result<b>
, into a function with signature Result<a> -> Result<b>
.
If we have a look at our controller, we want to chain several functions depending on the previous function Result. Each function uses the "normal world" value embedded in case of Success by the previous one, that's why we need this bind
function.
Chaining operations is in fact the goal of a Monad. To define a Monad, we need a data type (Result<a>
for example), 2 functions bind
and return
(which is just a function that allows to elevate a "normal world" value, i.e a -> Result<a>
for example), and some properties that must be enforced (not covered here). That's why bind
is also called a monadic function.
Use Computation Expressions in F#
I think it is enough for Monad theory for now :). Let's switch to F# computation expression feature, which allows to simplify our controller, removing the need for bind or "esoteric" functional operator.
F# computation expression lets you define your own F# construct in the following form (async and seq constructs are built this way):
1: 2: 3: |
|
Inside this construct, you can use several keywords like return
or let!
(said "let bang"). To use them, you must define a builder for your construct with some specific functions, and instantiate it. let!
is defined implementing the Bind
function, and return
is defined with the Return
function. Sounds good, no? So let's define and instantiate a result
computation expression using our previous bind
function:
1: 2: 3: 4: 5: |
|
And now we can use it to declare our controller:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
let!
calls the bind
function, switching to the end on Failure, ignoring the rest like exception does, but without any exception.
I find it far more readable! And you?
Want to learn more?
I have just shown one use case of the bind function and F# computation expressions. I encourage you to have a look to this very detailed series written by Scott Wlaschin, and in particular, there are more examples in this one comparing applicative and monadic styles for validation.
Full name: functionalarchitecturepart.addProductToCartController
val Failure : message:string -> exn
Full name: Microsoft.FSharp.Core.Operators.Failure
--------------------
active recognizer Failure: exn -> string option
Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
Full name: functionalarchitecturepart.bind
Full name: functionalarchitecturepart.toHttpResponse
Full name: functionalarchitecturepart.someFunction
Full name: functionalarchitecturepart.someFunction