Monads, Promises, and Fluent Programming
I read Stepan Parunashvili Inventing Monads, in which he starts with some small, simple functions and quickly runs into problems in their use. He mutates them until he ends up with monads, a hot feature I mostly associate with Haskell. These compose separate actions to get something that’s safe and predictable.
I wanted to redo this is Perl so I could make some other points. Stepan contrives a situation to get to a particular endpoint, which is what us tech writers do. He wants to show something about Monads and that’s why he follows the progression he does. He’s not trying to suggest a particular way of programming.
I’ll mostly follow Stepan’s progression, which starts with a set of functions that rely on the others:
You always have the
$id value, and you build up from that to get the profile and to get the display picture:
The problem is that this only works if everything is perfect. There’s a user with that ID, that user has a profile, and that profile has a display picture. First, I’ll set aside that I’d do all of this in a different way where I wouldn’t have to know the low-level details, such as needing the profile to get the picture. It’s an example and I’ll stick with that as presented.
As an intermediate step, Stepan invents the
Chainer, a composer that can recognize when the previous step has failed. The chain starts with the info at the bottom of the process and then goes through the steps to get to the final desired value:
If there’s a value, it uses that value as the argument to the next step. (For Perl people, the
=> in his code is a fancy way to define a lambda, not create a pair. It’s
SIGNATURE => CODE.).
In that code, each step is a new
Chainer object, either the new one with a new value or the current one. Stepan modifies that for the case where one of the functions already returns a chained thingy. In that case (in
merge) you don’t make a new
Since he always returns a
Chainer thingy, I don’t see how he gets around a failure. You get one of the thingys, maybe the initial one, and then call
when on it. The value that the
Chainer stored was for the previous step but now he tries to use it for the next step. I could be missing something in his setup, but he never mentions how things work when any step fails.
Not only that, I shouldn’t need the extra
merge method. I can look at the value to see if it’s a
Chainer sort of thing (in this case, using the new isa operator from v5.32). This might not be allowed:
From there, you can follow [Stepan’s post]((https://stopa.io/post/247) to learn more about how this resembles monads. I want to look at the particular problem and a few techniques for dealing with it.
So, lets try something else. How about Promises? I only want to go to the next step if the previous step works? Stepan mentions that the
Chainer sidesteps callback hell, but so does Promises:
Here’s what I think the Promise version of
Chainer. Each step leads to the next
This outputs the message for that step:
That’s fine. The chain happens. But what if I reject the
Now I get an error because I don’t have a handler:
I can handle that anywhere I like, but a
catch at the end works:
catch is just a
then with only a
Now expand that into something real. Create a Promise and resolve it with the user ID I want to find. That’s primes the pump.
After that, use a series of
thens that each do the next step in the process. Each
then only handles the
resolve branch. If the function call works (returns a defined value in this example), I return that defined value. That value becomes the argument for the subroutines in the next
then. If I make it all the way to the final
then with a resolved Promise, I assign to final value (the picture) to
$picture and output a message.
If something doesn’t work, I return a rejected Promise that knows why it failed. Since nothing else but the final
then handles the rejected branch, the interstitial
thens are effectively skipped and I can look in the argument list for the reason it failed:
Here are some runs, where I neglect to supply a user ID, give one I know doesn’t exist, and so on so I get all the responses:
But, that code is pretty ugly. One of the major problems (among others), is that I hard-coded the chain. That’s easy enough to fix—I’ll construct all the middle
thens from a table instead. Rather than check with
defined, I’ll add a new step to verify the input.
There’s a trick I like a bit too much, but it’s fun enough to justify sometimes. Sometimes this sort of thing is called fluent programming, but I’m going to stick with “method chaining”. The trick is handling errors in the middle of the chain.
I want to write something like this, where I have a chain of methods one after the other. This isn’t a good design for this problem, but it’s fine for showing the method chaining idea. Each method returns an object. It could be the same object or different objects. I don’t have to know too much about that:
So, how do I handle errors? Here’s the trick. If a method fails, I’ll return a null object that responds to any method name by returning itself. That soaks up the rest of the method chain without an error. When you want to know if the whole thing worked, you look at what you got back. This is the same object type technique I used in No ifs, ands, or buts, The Null Mull, and the StackOverflow answer How do I handle errors in methods chains in Perl?:
Here’s the null class. The
new creates it and the
AUTOLOAD handles any method not defined by returning the null object again. I handle
DESTROY, a special Perl finalizer method to break an infinite loop:
To handle everything else, stuff moves into a class. Each method either succeeds or returns a null object:
There are various ways that I can reduce this to create new classes on the fly with just the operations I want to do, but that’s some tricky code I don’t want to explain here.