I was reading http://www.haskellforall.com/2013/06/from-zero-to-cooperative-threads-in-33.html where an abstract syntax tree is derived as the free monad of a functor representing a set of instructions. I noticed that the free monad Free is not much different from the fixpoint operator on functors Fix.
The article uses the monad operations and do
syntax to build those ASTs (fixpoints) in a concise way. I'm wondering if that's the only benefit from the free monad instance? Are there any other interesting applications that it enables?
There is a deep and 'simple' connection.
It's a consequence of adjoint functor theorem, left adjoints preserve initial objects:
L 0 ≅ 0
.Categorically,
Free f
is a functor from a category to its F-algebras (Free f
is left adjoint to a forgetful functor going the other way 'round). Working in Hask our initial algebra isVoid
and the initial algebra in the category of F-algebras is
Fix f
:Free f Void ≅ Fix f
Similarly, right adjoints (
Cofree f
, a functor from Hask to the category of F-coalgebras) preserves final objects:R 1 ≅ 1
.In Hask this is unit:
()
and the final object of F-coalgebras is alsoFix f
(they coincide in Hask) so we get:Cofree f () ≅ Fix f
Just look how similar the definitions are!
Fix f
isFree f
with no variables.Fix f
isCofree f
with dummy values.(N.B. this combines a bit from both mine and @Gabriel's comments above.)
It's possible for every inhabitant of the
Fix
ed point of aFunctor
to be infinite, i.e.let x = (Fix (Id x)) in x === (Fix (Id (Fix (Id ...))))
is the only inhabitant ofFix Identity
.Free
differs immediately fromFix
in that it ensures there is at least one finite inhabitant ofFree f
. In fact, ifFix f
has any infinite inhabitants thenFree f
has infinitely many finite inhabitants.Another immediate side-effect of this unboundedness is that
Functor f => Fix f
isn't aFunctor
anymore. We'd need to implementfmap :: Functor f => (a -> b) -> (f a -> f b)
, butFix
has "filled all the holes" inf a
that used to contain thea
, so we no longer have anya
s to apply ourfmap
'd function to.This is important for creating
Monad
s because we'd like to implementreturn :: a -> Free f a
and have, say, this law holdfmap f . return = return . f
, but it doesn't even make sense in aFunctor f => Fix f
.So how does
Free
"fix" theseFix
ed point foibles? It "augments" our base functor with thePure
constructor. Thus, for allFunctor f
,Pure :: a -> Free f a
. This is our guaranteed-to-be-finite inhabitant of the type. It also immediately gives us a well-behaved definition ofreturn
.So you might think of this addition as taking out potentially infinite "tree" of nested
Functor
s created byFix
and mixing in some number of "living" buds, represented byPure
. We create new buds usingreturn
which might be interpreted as a promise to "return" to that bud later and add more computation. In fact, that's exactly whatflip (>>=) :: (a -> Free f b) -> (Free f a -> Free f b)
does. Given a "continuation" functionf :: a -> Free f b
which can be applied to typesa
, we recurse down our tree returning to eachPure a
and replacing it with the continuation computed asf a
. This lets us "grow" our tree.Now,
Free
is clearly more general thanFix
. To drive this home, it's possible to see any typeFunctor f => Fix f
as a subtype of the correspondingFree f a
! Simply choosea ~ Void
where we havedata Void = Void Void
(i.e., a type that cannot be constructed, is the empty type, has no instances).To make it more clear, we can break our
Fix
'dFunctor
s withbreak :: Fix f -> Free f a
and then try to invert it withaffix :: Free f Void -> Fix f
.Note first that
affix
does not need to handle thePure x
case because in this casex :: Void
and thus cannot really be there, soPure x
is absurd and we'll just ignore it.Also note that
break
's return type is a little subtle since thea
type only appears in the return type,Free f a
, such that it's completely inaccessible to any user ofbreak
. "Completely inaccessible" and "cannot be instantiated" give us the first hint that, despite the types,affix
andbreak
are inverses, but we can just prove it.which should show (co-inductively, or just intuitively, perhaps) that
(break . affix)
is an identity. The other direction goes through in a completely identical fashion.So, hopefully this shows that
Free f
is larger thanFix f
for allFunctor f
.So why ever use
Fix
? Well, sometimes you only want the properties ofFree f Void
due to some side effect of layeringf
s. In this case, calling itFix f
makes it more clear that we shouldn't try to(>>=)
orfmap
over the type. Furthermore, sinceFix
is just anewtype
it might be easier for the compiler to "compile away" layers ofFix
since it only plays a semantic role anyway.Void
andforall a. a
are isomorphic types in order to see more clearly how the types ofaffix
andbreak
are harmonious. For instance, we haveabsurd :: Void -> a
asabsurd (Void v) = absurd v
andunabsurd :: (forall a. a) -> Void
asunabsurd a = a
. But these get a little silly.