After continuing my foray into the functional with Giraffe and .NET Core, I have struggled with the code not quite reading very well. Various data sources are pluggable, i.e. they can be replaced with mocks if you don’t want to run other supporting systems while debugging. This pluggability along with the way Giraffe handles some of its configuration means that – while we would like to compose partially applied functions with either mocked or actual services baked in that are then passed in to the relevant handlers, we instead have to pass a HttpContext around everywhere. If you followed that link you probably wondered why we did not just use the strategy outlined in that blog series. I can only say that we did not understand fish operators at this point.
So we turn to the googles, and as usual the first or second hit lands us on F# for fun and profit. Scott Wlaschin introduces us to the Reader Monad. We watch his talks, we read his blog posts. A week of enthusiastic coding goes by.
Essentially – the reader monad allows you to write and construct all the code and only at the end plug in the HttpContext as the whole thing is evaluated and returned to the caller.
You make a Reader<‘env, ‘data> and you create functions to wrap and unwrap things in and out of the Reader. Then you make functions that mind their own business but they return Reader<‘env, b: and you allow the reader to handle some railway oriented programming for you. Essentially you lay the track all through the code but on the last line in the handler you put the train on the tracks and set it off, only then realising whether or not it ended up running through the failure cases or went all through the success track.
Wrappers, essentially, like Option<‘a>, IEnumerable<‘a> or Reader as described above, are introduced as Elevated things. The proper word seems to be Monadic types, but that does not seem to be a good term, as there is no need for a wrapper type to be a monad, although they can be. After a few specific examples the general case is presented and it turns out you can do a bunch of stuff with these elevated types only using functions like bind, return, apply and map. There is a brief epiphany as we experience a flicker of understanding of the monad laws.
A monad is just a monoid in the category of endofunctors, what is the problem?
Now I cannot explain the chaining of elevated and non-elevated functions properly. I know what bind does, and I can use it correctly on the first try. Return is obvious. Map and apply though… it’s like being ten years old again and round-robining commands until the compiler is happy. As it stands all I can do is link to the posts we have read and the metaphors that have been presented to me, that I have half understood and then tried to reconcile with other conflicting metaphors.
Inevitably the golden success case of “first I need this, then I pass it to this other thing and then I flip it, kick it and reverse it and then I return it” is soon replaced by “well, first I need this one thing, that I need to just check against this other thing, but then use it again in this third thing which I convert to this fourth thing that I might return if this fifth thing is true” and the nice chaining goes out the window and it makes you sad. Every let b = … feels like a let-down (hence the name).
This let = thing is apparently called applicative style, where you compose values, while chaining is called monadic style. At least it has name.
So after a while when you have been writing things like
let! a = coolFunc b c
return! a |> modifyInAnAwesomeWay
let! a = thingThatDoesDatabaseThings b
return! match a with
| Some data -> coolThing data
| None -> unCoolError
You start wondering what the heck it is you are doing. What are these things? It turns out they are computational expressions. But how do I make them? You essentially create a ThingBuilder class with members for bind and return, and then create a let thing = new ThingBuilder(), after which you can write expressions like:
let! unwrapped = funcThatGetsPassedToBind arg
To be a bit enterprisey I have come up with a draft of a brilliant thing. The F# Functional Maturity Model (FFMM):
- Can get samples to compile even with modifications
- Can get write small programs that do useful things in prod
- Can write software systems that fulfill a business purpose
- Can see how bad OO in F# looks and strive to create functional style code
- Attempt to write a monad tutorial
- See the beauty of computational expressions and want to use them everywhere
- Stops being afraid and starts to love Kliesli composition (the fish operator)
- Realise computational expressions are an antipattern and curse their existence
As you can see I have yet to ascend the ladder enough to know what all the levels are, but I am currently on 6 I believe, but given the hate people have for do in Haskell, it seems that there must be a level where you realise the computational expressions are of the devil and must be eliminated at all cost, but at the level where I am now, they do indeed seem like they cut away a lot of plumbing code where I otherwise map and bind to call various things. Also, I see the fish operator everywhere so clearly it must be awesome. I now at least understand what it does, but I can’t say it fits very well in the code I want to write. I’m sure that is an epiphany for a later date.