Free functors, free applicatives, free monads II Part IConsider the definitions (1), (2), and (3) of the "free monad" from the previous post,
Free
1 F t = Fix
s( t + F s )
Free
2 F t = Fix
S( t + F t + ∃a. S a * (a -> S t) )
Free
3 F t = Fix
s( t + ∃a. F a * (a -> s) )
Let us rewrite them in Haskell's "long form", that is, as representable functors, while giving the individual type constructors suggestive names:
data M
1 t where
Pure :: t -> M
1 t
Join :: F (M
1 t) -> M
1 t
data M
2 t where
Pure :: t -> M
2 t
Wrap :: F t -> M
2 t
Bind :: ∀a. ( M
2 a * (a -> M
2 t) ) -> M
2 t -- we replace ∃a by ∀a since we are moving it out of a contravariant position
data M
3 t where
Pure :: t -> M
3 t
Bind :: ∀a. ( F a * (a -> M
3 t) ) -> M
3 t
Then we notice two curious things:
- the type signatures of the "Pure" and "Bind" constructors are very similar to the types of the "pure" and "bind" methods of a monad:
pure :: ∀t. t -> M t
bind :: ∀t.∀a. M a -> (a -> M t) -> M t
The definition of the "free monad" looks almost like listing these two method's type signatures!
- however, the type "F a" is used in M
3 instead of "M a", and, as if to compensate this, there is an extra type constructor I called "Wrap", that injects "F a" into the free monad.
There is no doubt that all these definitions of the "free monad" are "correct" (i.e. both define a monadic functor). Questions remain: for instance, there are two places in the "bind" method where we use "M t" recursively. What if we wanted to use "F t" instead of "M t" in one of these places (or both)? There are four possibilities, of which two are not yet written down:
data M
4 t where
Pure :: t -> M
4 t
Bind :: ∀a. ( F a * (a -> F t) ) -> M
4 t
data M
5 t where
Pure :: t -> M
5 t
Bind :: ∀a. ( M
5 a * (a -> F t) ) -> M
5 t
Do these work, do they need additional type constructors to work, or do they fail even with additional constructors?
A partial answer was given in this blog post:
http://blog.higher-order.com/blog/2013/11/01/free-and-yoneda/The gist of that blog post is that M
3 is obtained from M
1 by using a "free functor" instead of a given functor F. So let us now look at how a "free functor" works.
(The "free functor" is sometimes called a "co-Yoneda", but so far I don't think the connection to the Yoneda lemma is at all helpful for understanding the "free functor".)
Free functorsA functor is a type constructor F with an additional morphism called "fmap" in Haskell.
fmap :: ∀t.∀a. F a -> (a -> t) -> F t
If we have a type constructor F that is not a functor, we can add some more structure around it and define a functor G like this:
G t = ∃a. F a * (a->t)
The type constructor G is a functor since it is exp-poly in "t" and uses "t" only in a contravariant position.
We call the functor G the "
free F-functor".
It is then easy to see that M
1 applied to the free F-functor is the same as M
3 applied to the type constructor F.
The "Haskell long form" of the type definition is
data Ff
1 t where
Ffmap :: ∀a. F a * (a -> t) -> Ff
1 t
Note again the similarity of this and the type signature of "fmap". We just used the product F a * (a->t) -> Ff t instead of writing F a -> (a->t) -> Ff t, so that the implementation of this data type becomes more obvious.
Let us ask the same questions about the construction of the free F-functor as we asked about the construction of the free F-monad:
- How to derive this construction systematically?
- Can we replace "F s" by the recursive application of "Ff s" in the definition of "Ff" above? For instance, what about the alternative definition (inspired by comparing M
2 and M
3 above):
data Ff
2 t where
Wrap :: F t -> Ff
2 t
Ffmap :: ∀a. Ff
2 a * (a -> t) -> Ff
2 t
Is this definition equivalent to Ff
1?
To find answers, I found it helpful first to consider the concrete implementations of the two versions of the free F-functor according to definitions Ff and Ff
2.
Implementing the free functorThe "fmap" operation of the free F-functor is defined like this: We have a value (fa*g) of type Ff
1t, where fa : F a and g: a->t, and a function value h of type t -> s. We now define fmap on these two values in the only way possible:
fmap
Ff1 (fa*g) h = fa * (h ∘ g).
So the value "fa" inside the product F a * (a->t) remains unchanged, but the morphism g gets composed with h.
Any value of type Ff
1t is therefore either created from scratch, where we supply some value "fa" of type "F a", or is obtained using "fmap", where this value "fa" remains unchanged.
It is natural to provide a "constructor" that injects a value of type F t into the free F-functor:
wrap :: F t -> Ff
1t
wrap ft = ft
F t * id
t->tIf values of type Ff
1t are always made either by this constructor or by fmap, then the initial value "ft" injected into Ff
1t will remain unchanged.
Note that we cannot "unwrap" a value of type Ff
1t because, even if we find that this value is of type F t * t->t, we cannot determine whether the function of type t->t is equal to identity.
Now, consider the second definition of the free F-functor. Now "Wrap" is part of the definition.
To define the function "fmap" as required for the functor instance, we now need to consider two cases. A value of Ff
2t can be either a "wrapped" F t or a "recursive case" Ff
2a * (a -> t). For a "wrapped" F t, we first convert it to Ff
1t * (t->t), and then "fmap" is defined exactly as in Ff
1t.
If we now create a value of Ff
2t using Wrap and then apply some "fmap" to it, we will get a value of the form
Wrap(F a) * (a -> t).
If we continue to apply "fmap" to this value, the part "Wrap(...)" will remain unchanged, just as in the case of Ff
1t.
However, now we can define "unwrap" that will distinguish between a value of Ff
1t which has just been constructed using "Wrap", and a value of type Ff
1t to which one or more "fmap"s have been applied. (The compiler might use this information to optimize the code.)
The price for this is relatively small: the type Ff
2t allows "nested" instances of Ffmap, such as
Ffmap ( Ffmap (...))
which cannot be a result of using Wrap or Fmap, but are nevertheless valid values of type Ff
2t. In other words, the same value of type Ff
2t can be encoded in many different and theoretically equivalent ways. This is a redundancy that, probably, does not actually affect any real-life applications of the free functor.
As we see, the definitions Ff
1t and Ff
2t are not fully equivalent; Ff
2t is a redundant encoding that in principle allows more optimization.
To get the best of Ff
1t and Ff
2t, we can retain Wrap but define the recursive case so that nested instances are not allowed:
data Ff
3 t where
Wrap :: F t -> Ff
3 t
Ffmap :: ∀a. F a * (a -> t) -> Ff
3 t
Now, Ff
3 t has two variants: one, Wrap, to which no "fmap" has been applied, and another, Ffmap, which is a result of applying fmap. The implementation of fmap will have to pattern-match on these two cases, converting a Wrap to an Ffmap.
-- Haskell
fmap (Wrap ft) = Ffmap ft id
fmap (Ffmap fa g)) h = Ffmap fa (h . g)
unwrap :: Ff
3 t -> Maybe (F t)
unwrap (Wrap ft) = Just ft
unwrap (Ffmap _ _) = Nothing
(to be continued)