One of my favorite things as a programmer is consuming a clean API, and many of my favorite APIs can be described as fluent. This is a pattern where computations can be chained off of each other. For example, you may have heard of an object oriented design pattern called the builder pattern. First, let’s see what a non-fluent version looks like with some pseudo code from Wikipedia.
1Construct a CarBuilder called carBuilder2carBuilder.setSeats(2)3carBuilder.setSportsCar()4carBuilder.setTripComputer()5carBuilder.unsetGPS()6car := carBuilder.getResult()
Above we are building a car, and we have one programming statement for each feature we want to add to it. But when I see this pattern out in the wild, I usually find that it’s implemented with a fluent API. Let’s see an example of a fluent builder API in C#:
1var builder = new FluentBuilder ();2product = builder.Begin()3 .Engine4 .SteeringWheel5 .Tire()6 .Tire()7 .Build();
Here we’ve built some kind of vehicle in just two statements: one to invoke the constructor, and the other to specify all the components that we want in our vehicle. It’s the return type of each of these methods that makes this possible. Each one has to return the builder so that you can keep calling the builder functions. If you write C#, you’re probably familiar with more types of chainable computations, such as
IEnumerable<T> operations. Here’s one that multiplies a list of numbers by 3 and then returns the even ones.
Ruby programmers may also find this familiar, and it likely shows up in many languages. But when you start writing ReasonML, you may find yourself writing something like this to achieve the same result from a collection type:
This is because Reason’s
list type does not expose methods for performing computations on itself. Rather, there is a
List module that exposes pure functions that operate on lists! But fear not: we can still achieve some fluency by using this wonderful thing called a pipe.
Using Pipes to Add Fluency
You might be familiar with piping from the command line on *nix operating systems, or if you’ve done much functional programming. If not, don’t worry, the concept is straightforward! A pipe merely takes the value on its left side and applies it as an argument to the function on the right! Let’s see an example for clarity.
Here we have a function that adds one to any integer, and two examples of calling that function using the number two as input. We can invoke our function in the standard way by calling
add1(2), or we can pipe the number two into our function by calling
2 |> add1. So let’s apply what we learned to our list operations! First, here’s a trick you might want to know:
List.map takes two arguments, but if I only supply the first one, the result will be a partially applied function. That means that
List.map(someFunc) is actually a function that accepts a list and returns another list! That means I can do this:
If I take that a bit further, it also means that I can do this:
Cool! We now have a single statement instead of three, and our function calls are less verbose. This feels a lot better to me, but in fact there is more we can do to make this API safe and enjoyable to consume. To see why it might be important to go further, before moving on to the next section, ask yourself this: what if one of these list operations threw an exception?
Fluent Error Handling
With the first rate type system and pattern matching that come with ReasonML, you can eliminate most types of runtime errors, but there are still some things that can blow up. For example, check out the description of
List.tl in the ReasonML docs:
Return the given list without its first element. Raise
Failure "tl"if the list is empty.
Let’s create a scenario where this can fail by multiplying our integers by two, filtering out the even integers (i.e. all of them), and then asking for the tail of the list.
If you run this code, it will raise an exception because the list is empty when we call
List.tl. ReasonML has a couple ways of dealing with exceptions. You can use a
switch or a
try/catch to handle this case. But we can actually make the error handling part of our fluent API by learning some new techniques!
Handle errors with a wrapper type
The first step towards improving our API is to wrap the type that we’re working with, so that we can represent both regular values and errors with a single type. ReasonML has a built in wrapper type called
Option that would work fine for our purposes, but it’s really easy to make our own, so why not?
Here we are saying that there are two ways to represent a list: a
Value that contains the list, or a
Failure that we’ll use to indicate that a list operation raised an exception. We’d now like our list operations to return this type instead, but of course they don’t yet. So let’s start fixing that with the operation that’s giving us trouble: the tail call.
So now if we call
tail instead of
List.tl, we’ll get our Failure type instead of an actual exception. But this doesn’t allow us to chain our calls! I can’t pipe the result of tail into another function that operates on a list, so I don’t have a fluent API. Fortunately, we can get this capability back by learning about the
Function Chaining Unbound!
ReasonML has an operator that looks like
>>= and goes by the name
bind, and we use it for performing chainable computations on wrapper types. We get to define how this operator works according to the following rules:
- It accepts the wrapper type as the first argument.
- The second argument is a function that takes the type we want to wrap, and returns the wrapper type.
- It must return the wrapper type.
That means that our
bind operator will need to accept a
chainableList as a first argument, and a function
list => chainableList as a second. Here’s how we’ll define ours:
Let’s break this down! The
m argument is our incoming
chainableList, and our
f argument is the function that we want to apply to the list that we’ve wrapped. As long as
m contains a list, it’s safe to call the function, but if
m is a
Failure then we can just return a
Failure. So what does this give us for our API? Let’s take a look:
1 |> tail >>= tail >>= tail
Here we start by piping a list with a single element into our
tail function. This is going to return a
Value that contains an empty list! After that, we use our bind operator to keep feeding the result of
tail into subsequent
tail calls. The second and third call will return
Failure and we won’t have to worry about recovering from exceptions. We can chain calls to tail as many times as we want and our app will never explode!
So how about all those other operations, like
filter? None of them throw exceptions, so what we can do is define a generic function for wrapping operations that won’t fail. I’m actually going to use two functions to do this:
return function is often used in scenarios like this as a way to wrap simple values in a wrapper type, but for our case we will get more mileage if we build off of it using the
wrap function defined above. The first argument to
wrap is a function that takes a
list and returns another
list, which is exactly what our computations like
filter do. The second argument is the list that we feed into the function
f. We then use the
return function to wrap the output of this computation in a
Value to convert it to our
chainableList type. Now our fluent API looks like this:
Let’s break it down!
- Wrapping our list in a
returncall converts it to a
chainableListthat we then feed into the subsequent operations using the bind operator.
- The first
wrapcall is our map operation.
List.map(n => n * 2)is a partially applied function; it accepts a
listand returns another
list, and is therefore the type we need for the first argument of
wrap. The same is true of our
filteroperation on the following line.
- But our calls to
wrapare also missing their second arguments! The
wrapfunction is supposed to take a list! Therefore, it’s a function that takes a
listand returns a
chainableList, which is the type expected for the second argument of our
So on one hand, it might seem like a lot to unpack because we have so many functions returning other functions. But take another look at the result! We’ve achieved a pretty darn fluent API that’s also safe to use without fear of runtime errors! And guess what? There’s a bonus!!
So far I’ve been selling this bind operator as a way to create fluent APIs that are safe to use. But now that all of our operations are going through this bind function, we can add some behavior to it. For example, we might want to do some logging. If our operations are asynchronous or computationally expensive, we might want to gather metrics on how long they take and how often they fail. We can do this in one convenient place without sacrificing the friendliness of our API!
Today we learned about building fluent APIs in ReasonML. We started with a technique called piping to add fluency to a series of functional operations. We then took that concept further by using binding and wrapper types, which allows us to handle exceptional cases neatly and also augment operations going through our pipeline with additional functionality. Check out my sketch to see all the code we wrote today, and make sure to fork it and play around with it too!
In this post I’ve used a technique that is well known among functional programming enthusiasts, and I have intentionally avoided mentioning it by name. I won’t go so far as to say it’s forbidden here, but I often find discussion of this technique to be highly theoretical and lacking in pragmatism, so I’m trying to reverse the approach and talk about the problem we’re solving first. If you already know the technique, feel free to congratulate yourself quietly, but please try not to scare others away!
Some related things you might want to read about:
- If you are new to ReasonML and looking to learn more, the docs are good and getting better all the time.
- Check out this example of a fluent builder API in C# to learn more about the builder pattern, fluent APIs, and object oriented design.
- This post was inspired by a series about computation expressions from F# for fun and profit. If you’re unfamiliar with F#, it’s Microsoft’s version of OCaml, so the material translates quite well since ReasonML is just an alternative syntax for OCaml. I really recommend trying it out, and this website is a great place to learn about it. The material there is top notch, and this series is no exception.
- The technique I describe in this post is also a form of what’s known as railway oriented programming, which you might think of as a way to short circuit your programming flow without relying on early returns.
- All of the code we wrote for this example can be found in this sketch. Please fork it and play around with it! Sketch is a fantastic and simple online REPL for ReasonML, and I’ve absolutely loved using it.
- If you are interested in the future of async/await in ReasonML, or if you enjoyed the F# post on computation expressions that I linked above, you’ll definitely want to keep an eye on let-anything by Jared Forsyth!