Combining Types as Effects to Describe an Application

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.

“closeup photo of person's hand” by Daniel Jensen on Unsplash

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 async function 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 Maybe and 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 Promise, Maybe, and 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