Monads are a programming pattern reinforced by category theory that provides for computations which support additional operations, chaining each to the end of the last and transforming the value being managed. They provide a practical way to model sequences of operations, allowing the user to pass the computation to other functions to add additional steps as needed.
A Practical Platform
As a stopgap in my daily work, I turned to TypeScript for a relatively easy-to-use way to keep track of my types and turn more of my thinking into logical assertions that can be validated as I work. I discovered that, over time, Microsoft has turned TypeScript into a very capable and flexible language. With the right settings and tslint configuration, you can use it in a strict way that protects you from a lot of chaos.
TypeScript uses structural typing (similar to the “duck typing” of popular languages like Python), which means that it pays attention to the properties of an object rather than the identity – meaning that an object type is compatible with any other object type with matching properties. In practice, this is sufficient to validate the expected behavior of things like monads. It also provides an increasingly flexible way to manage generic types, including conditional types based on what the user passes in.
I’ve always missed the confidence that monadic programming gave me, however – the clear understanding of each step in a process, the ability to add new operations and transform values easily, the emphasis on reuse and composition, the protection against things like
undefined. I’ve never found anything user-friendly enough to use with a team, however. There are libraries like fp-ts that do an outstanding job of providing a comprehensive and full-featured set of functional tools, but they can be overwhelming to the newcomer. I want something that a team can quickly get up to speed on and begin using in an intuitive way.
At Community Funded, we began writing small utilities to help us use TypeScript in a more predictable way, like a small
Maybe (also known as
Option) class to help us represent optional values without relying on
undefined. As these tools grew, we decided to break them off into a separate library that we could share with the world and use in our open source projects. As a result, Monadism was born!
The goal is to build data structures that help us achieve greater determinism and prevent errors, without being inscrutable and hard to maintain. With a solid type structure, you can allow the static analysis to be your guide as you build, maintain, and optimize your code.
A strong type system can not only prevent errors, but also guide you and provide feedback in your design process.Mark Seemann – Type Driven Development
Maybe Just Nothing
Let’s look at some practical examples using the
Maybe monad. A
Maybe is an optional value that can be represented as “absent” via the
Nothing() constructor, or “present” via the
Just() constructor. It allows us to reduce the amount of conditional or boolean logic in our code. To see how this can help, let’s look at a typical operation using
undefined for optional values.
Imagine we have a function that retrieves a
Page from an API. It returns a
Promise with a yield value that can be
undefined if no
Page is found. This object has some nested properties on it that we want to use if a
Page was found. To use them, we need to fill our code with verbose conditional operators.
You might think, “This is 2019! We can use destructuring!” Sadly, TypeScript has other opinions.
Okay, so how can
Maybe make this better?
Maybe<Page> allows you to produce a much more declarative operation. Rather than using conditional logic and “falsy” values to imperatively control the flow, you describe the results you want and let
Maybe handle the flow for you. This is especially useful for extending and transforming the operation if you re-use it elsewhere, or transforming your wrapper type from
Maybe into other compatible data structures to use it in different ways.
Code that inverts control to the caller is generally easier to understand. […] If you don’t have any trouble reasoning about code that overflows with booleans and conditionals, then more power to you! For the rest of us, however, conditionals complicate reasoning.John A. DeGoes – Destroy All Ifs
State management is a problem as old as (software engineering) history. There have been a variety of ways devised to keep track of the current status of an application, and they all have pros and cons. One strategy that has enchanted functional programming enthusiasts is functional reactive programming.
FRP is a general paradigm for describing dynamic (time-varying) information. […] FRP expressions describe entire evolutions of values over time, representing these evolutions directly as first-class values.Conal Elliot – Essence of FRP
In reality, most systems in popular software engineering that call themselves “FRP” aren’t “denotative” or have “continuous time”. For more information about those concepts, check out Conal Elliot’s “The Essence and Origins of FRP” talk.
In general, however, these libraries are inspired by the representation of events as a stream over a period of time. Though they may not mathematically qualify as functional reactive programming, they can provide a helpful pattern for modeling and understanding events over time.
In Monadism, we’ve ported the novel purescript-signal library, inspired by Elm’s Signals, to TypeScript. The foldp method allows us to create a “past-dependent” Signal. Each update from the incoming signals will be used to step the state forward. The outgoing signal represents the current state.
The Mario example does a great job of demonstrating Signal’s usefulness. The main logic loop is expressed like this:
You can see the full module in the Monadism repository, but this is how the jump function is implemented. The jump input is a
boolean value representing whether the space bar is pressed at a given moment in time or not.
If the space bar is being pressed and Mario is not airborne yet, then the
dy value representing the
y velocity is set to the appropriate value. If the space bar is not being pressed and Mario is airborne, then he begins to feel the effects of gravity and his
y velocity is decreased.
marioLogic function takes the current inputs and returns a function that takes a previous state and returns a next state. This means that the state of the future depends on the state of the past – a
The main run loop waits until the DOM is loaded, and then creates a Signal based on animation frames. This yields a stream of events for each animation tick.
For the inputs, we create a Signal that yields a stream of values based on whether the given key codes are pressed. Then, we apply them in order to the
getInputs function to populate the plain object representing all of the inputs at once.
For each tick of the
animationFrame(), the inputs are sampled and then piped into the
marioLogic function, which processes updates to the logic ever since an
initialState is established (not pictured in the snippet).
Just Getting Started
The Monadism library is just getting started, but the plan is to keep it lean and practical. Tools that end up not getting a lot of real-world use will be moved into a
contrib package, allowing the library to grow while keeping the core focused on the most widely useful utilities. Documentation and tutorials will grow over time, and hopefully Monadism will become an easy-to-use way to introduce sound functional programming to your production application!
Let me know if you have suggestions or feedback, and find me on Twitter if you want to talk more!