Using Tagless Final DSL with Monads in Scala 3
Flat-map me baby, where pure love meets eager evaluation
In my previous post on tagless final I explained using various examples how this abstraction allows developers to define interpreters for grammars or languages without explicitly committing to a concrete representation. In this post I explain how to combine tagless final with monads using a Scala program example I wrote and posted on Github.
Monads matter because they give structure to effectful computations and make them compose predictably. In functional programming a value can live in many computational contexts, for instance a successful result, a pretty printed form or an error accumulator. A monad supplies three things: a way to embed a plain value (pure
), a way to transform a wrapped value (map
) and a way to chain operations that themselves produce wrapped values (flatMap
) or (bind
). The laws for these operations, left identity, right identity and associativity, guarantee that the order in which you refactor chained calls never changes the observable outcome.
Including monads in the expression-DSL example turns a handful of simple primitives into a reusable little language. Without the monad the add
and lit
operations would have to return raw integers, which fixes the meaning of every program at the moment it is written. Once each operation returns an abstract F[Int]
instead, the same source code can be interpreted in multiple ways.
With
Container
the monad is the identity function, so the program evaluates to anInt
.With
Pretty
the monad records the rendering steps and yields a human readable string.With
Id
the monad isEither
, so every operation may fail and short-circuits if it does.