I can only imagine the face I made the first time someone referred to the type of an application. Coming from a Delphi, .NET, and web background at the time, I assumed the type of an application was Web vs Desktop vs Command Line. What I came to understand was that types can be used to model effects which can then determine what your application does.
The Zombie apocalypse of Async..Await
The first time I saw a hint of this sort of thing was when I first saw
async..await in C# when
Task came on the scene and it’s even more prevalent now with
Promise. The sales pitch is simple; if you’re accessing external data you can do that asynchronously by making the method
async and adding
await statements. The reality is that the function now has to return a
Promise rather than its normal type. So if the method returns a
number in synchronous code it must return
Promise<number> in asynchronous code. That seems simple enough, but what that means is that anything that calls the
async method now also has to return a
Promise or you’re forced to block somewhere in the chain which removes the benefits of being asynchronous in the first place.
It just takes one
asyncfunction to infect the rest of your application.
The Hidden Zombies
Once I started writing pure functions I noticed there were other places where my code had been infected. The infamous null check was scattered implicitly throughout my code as well. That’s when I learned about using
Maybe as a way to safely and explicitly handle incomputability. I soon realized exceptions were also implicit and learned how to use
Either to encode my exceptional cases in a type-safe manner. The thing about
Either is that they started doing that same Zombie infection thing that
Promise did in my code.
So what type of application do you have? Well, to continue our undead analogy, it’s basically the combination of all the different zombies you have.
If you have asynchronous code and potentially throw exceptions then you’re combining two zombie types and you have an application that can be modeled as a
Promise<Either<Error, A>> where
Error is a type that handles application errors and
A is the type your program returns. For instance, we could say that any exception at the
Promise level is like a 500 status code, our
Error type will be returned for 400 level status codes, and our
A is what is returned for a 200 status code.
I think that combination holds up really well for most things, but most applications also accept some sort of input and it would be nice to include that input in our model. It turns out that we can do that easily by simply saying that our program is a function!
(config: Configuration) => Promise<Either<Error, A>>. As it turns out, this concept is already a well known structure just like
Either. It’s called
Reader and that means we’ve once again discovered a well known pattern. Almost every application I’ve ever built could be modeled as
(config: Configuration) => Promise<Either<Error, A>> and the best part is that we can combine those types into a
ReaderPromiseEither<Input, Error, Result> type and then compose smaller programs to make bigger programs.
For an excellent example motivating each of the effects, I highly recommend the following post by gcanti