From Expression
to Free
We've been defining the function, fold
, the same way for a while. However, there's another way to write the function. To help you understand the upcoming code, we'll rewrite it in this new way:
fold :: forall f a. Functor f => (f a -> a) -> Expression f -> a
fold f (In t) = f (map (fold f) t) {-
... which can be rewritten using infix notation -}
fold f (In t) = f ((fold f) <$> t) {-
... which can be rewritten to not pass `f` through recursive calls -}
fold f = go where
go (In t) = f (go <$> t) {-
... which can be rewritten to use "case _ of" to pattern match -}
fold f = go where
go in_t = case in_t of
In t -> f (go <$> t)
With that out of the way, let's compare Expression
to Free
. We can see that Expression
is really just a variant of the Free
monad without the Pure
constructor.
newtype Expression f
-- no pure here...
= In (f (Expression f ))
newtype Free f a
= Pure a
| Impure (f (Free f a))
How would we rewrite our solution from before to use Free
instead of Expression
? Value
is replaced with Pure
.
To see an example of this for just Value
and Add
(Multiply
is excluded) and to understand its code, refer to the below code and table before viewing ADT8.purs.
-- when Value and Add were both `f`
fold f = go where
go in_t = case in_t of
In t -> f (go <$> t)
-- when Value is simply Pure now
fold f = go where
go free = case free of
Pure a -> a -- Value
Impure t -> f (go <$> t) -- Add
xgrommx's code | Our code |
---|---|
Free ExprF a or Expr | Expression (Coproduct Value ExprF) a |
lit | value |
iter | fold |
iter k go | fold algebra expression |
Left f Right a | AddF ValueF |
A Quick Overview of Some of Purescript's Free
API
Purescript's Free
monad is implemented in the "reflection without remorse" style, which adds complexity to the implementation. Thus, rather than redirecting you there, we'll explain the general idea of what the code is doing.
LiftF
The Free
monad has its own way of injecting a value into it called liftF
. It can be understood like this:
-- Before
someValue :: forall a. a -> Expression SomeFunctor
someValue a =
In (SomeFunctor a) -- or `inj (SomFunctor a)`
-- After...
liftF :: a -> Free SomeFunctor a
liftF = Impure (SomeFunctor a)
Wrap
LiftF
is useful, but it won't let us compile the examples we will show next because it expects the a
to be any a
. In cases like AddF
/MultiplyF
, sometimes that a
has to be Free AddF
/Free MultiplyF
instead of just the Int
type. In such cases, we can use wrap
:
-- Using `liftF`
add :: Int -> Int -> Free AddF Int
add x y = liftF $ AddF x y
-- the above 'add' definition does not allow us to create
-- nested adds
-- Using `wrap`
add :: Free AddF Int -> Free AddF Int -> Free AddF Int
add x y = wrap $ AddF x y
Other Functions
foldFree
interprets the Free into some other monad (e.g. Effect
, etc.).
runFree
computes a "pure" program described in Free
(by 'pure,' we meant that Free
only stores the instructions the program would execute, but does not run them).
Compilation Instructions
You can run the upcoming code examples using these instructions:
spago run -m Free.AddAndMultiply