The Zora Monad, a new concept by PureFunctor, is a truly mind-blowing idea. In this article, I'd like to describe what it is, how it works, and why I think it's so amazing.
What does it do?
The Zora monad is a way to model effect hierarchies. Effect hierarchies are families of effects that are related to each other. The best way to imagine this, by way of analogy, is thinking of a traveling theater troupe.
The troupe knows that it will perform its show in two different types of venues:
- smaller theaters with limited budgets for effects, and;
- well-equipped theaters with all the bells and whistles - lavish light racks, vaulted ceilings for quick scenery changes, etc.
The thespians come up with two versions of the show - one that is a no-frills rendition of the base text, and one that adds all sorts of extravagant lighting, sound, and scenery.
For their tour to be possible, the artists need to be able to alternate between these two versions with ease. So they come up with a system that intelligently and gracefully omits the special effects in the more modest version while still providing audiences with a great show.
Relating the analogy back to the Zora monad of type Zora a
, if a
is the unadorned text of the play, then the monadic effect Zora
encapsulates the degree of effects that will be added on top of the text depending on the theater we're performing in. As we step through or interpret the monad (analogous to performing the play), we need to keep track of two main things:
- where do the "extra effects" intervene; and
- how do we leave out these effects gracefully when we can't use them.
How did the need for Zora come about
Zora was created by Justin, aka PureFunctor, to improve static site generation and server side rendering in purescript-deku
. Before Zora, Deku relied on a Byzantine assemblage of ad hoc typeclasses to achieve SSR. This resulted in sprawling error messages and poor type inference.
At a basic level, a statically-rendered site needs HTML and CSS without the accoutrements of JavaScript, not unlike our play being performed in the smaller theater. It also needs to integrate nicely with JavaScript after the browser's initial render.
PureFunctor's insight was that the typeclass machinery used to achieve SSR in the old Deku could be replaced by a dedicated monad that "forked" whenever there came a point where the more elaborate context (JavaScript) needed to be used. One prong of the fork contains a code path with no JS, and the other prong contains the same path enriched with JS. SSR would then take the left prong, whereas hydration or SPAs would take the right prong.
How does Zora work
The (simplified) definition of Zora goes as follows:
data Zora a =
LiftEffect (Zora a) (Effect (Zora a))
| LiftST (ST Global (Zora a))
| Stop a
For those of you familiar with the Free Monad, you'll notice that the definition is similar-ish.
data Free f a =
Continue (f (Free f a))
| Stop a
The crucial differences are:
- Zora is not "free". We know exactly what
f
can be (ST Global
orEffect
), so it does not need to be open-ended; - There are two effectful branches, not one; and
- (this is the important bit) The "stronger" branch, meaning the one that can do more stuff (
Effect
) contains a fallback operation that we execute if we are interpreting on the weaker branch. That fallback is the first argument toLiftEffect
.
So as we step through our algorithm, when we are doing SSR and we encounter LiftEffect
, we use the first argument, whereas when we're in Effect
, we use the second.
From a denotational standpoint this is all well and good, but it seems sort of magical that we can pull an a
out of thin air in LiftEffect
whenever we're not in an effectful context. For example, let's assume that the second argument to LiftEffect
is a network call that produces a
. How can we get an a
for the first argument without our network call?
For this to be work, we need to use Zora
in a context where its monadic effects can be substituted with filler or no-ops. This is exactly how Deku operates: it uses a style called continuation passing where the value we care about is always in the contravariant position of a type, ie a -> Zora Unit
, and the monad (Zora
) produces junk (Unit
) as a byproduct of its side-effect. a
is passed down a chain of consumers until it reaches a final subscription that specializes Zora
either to Effect
or ST Global
.
Why is this a big deal
I think Zora can lead to a whole body of new practical applications, if not research, on effects systems. SSR in Web Development is one area, and here are some others that are waiting to be disrupted by Zora:
- Simulations versus a more expensive "real deal", where the real deal builds on top of the simulation with extra effects.
- Test code versus production code, where the behaviors of the code is mostly similar but diverges in a few places like credential fetching.
- Machine learning, where a trained model representing a base effect system can be enhanced to achieve additional effects via transfer learning.
The thing that these all have in common is that they operate on families of related effects instead of (a) totally unrelated effects; or (b) stacked effects.
a) For totally unrelated effects, there's the Free Monad. If you have absolutely no idea how your monad will be interpreted, you need to leave its interpretation entirely open-ended, or free.
b) If you have various effects working together, like reading from some environment and making network calls and potentially throwing exceptions, the standard way of solving this is using a monad stack via a transformer library or an extensible effect library.
The Zora monad is different: we know exactly what classes of effects we are operating on, so we don't need a free monad, and our effect systems operate separately, not together, so there's no need for a stack.
So what is Zora? As we saw in the definition of Zora, the stronger context needs to be able to produce a stand-in value when it's encountered in the weaker context. This sounds very comonadic: comonads must always produce a value on demand via the extract
function. In general, Comonads like Cofree
resemble tuples because they need to hold something and a lil' bit extra. Zora is no different: the definition of LiftEffect
is a tuple that holds both something effectful and a non-effectful stand-in. So Zora is a monad that exhibits non-trivial comonadic properties. Just like, in Zelda, Zora is a water-dwelling creature that exhibits non-trivial Hylian properties (and is even known to flirt with the occasional Hylian). It would be interesting to see where this line of thinking leads and if there are any new typeclasses that could be derived from the use of Zora.
Top comments (0)