In the recursion-schemes package the following types are defined:
newtype Fix f = Fix (f (Fix f))
newtype Mu f = Mu (forall a. (f a -> a) -> a)
Are they isomorphic? If so, how do you prove it?
In the recursion-schemes package the following types are defined:
newtype Fix f = Fix (f (Fix f))
newtype Mu f = Mu (forall a. (f a -> a) -> a)
Are they isomorphic? If so, how do you prove it?
Copyright © 2021 Jogjafile Inc.
Yes, they are isomorphic in Haskell. See What is the difference between Fix, Mu and Nu in Ed Kmett's recursion scheme package for some additional remarks.
Let's begin by defining functions to perform the conversions:
To show those functions witness an isomorphism, we must show that:
From
Fixand backOne of the directions of the isomorphism comes off somewhat more straightforwardly than the other:
The final passage above,
cata Fix t = t, can be verified through the definition ofcata:cata Fix t, then, isFix (fmap (cata Fix) (unfix t)). We can use induction to show it must bet, at least for a finitet(it gets more subtle with infinite structures -- see the addendum at the end of this answer). There are two possibilities to consider:unfix t :: f (Fix f)is empty, having no recursive positions to dig into. In that case, it must be equal tofmap absurd zfor somez :: f Void, and thus:unfix tis not empty. In that case, we at least know thatfmap (cata Fix)can't do anything beyond applyingcata Fixon the recursive positions. The induction hypothesis here is that doing so will leave those positions unchanged. We then have:(Ultimately,
cata Fix = idis a corollary ofFix :: f (Fix f) -> Fix xbeing an initial F-algebra. Resorting directly to that fact in the context of this proof would probably be too much of a shortcut.)From
Muand backGiven
muToFix . fixToMu = id, to prove thatfixToMu . muToFix = idit suffices to prove either:that
muToFixis injective, orthat
fixToMuis surjective.Let's take the second option, and review the relevant definitions:
fixToMubeing surjective, then, means that, given any specificFunctorf, all functions of typeforall a. (f a -> a) -> acan be defined as\alg -> cata alg t, for some specifict :: Fix f. The task, then, becomes cataloguing theforall a. (f a -> a) -> afunctions and seeing whether all of them can be expressed in that form.How might we define a
forall a. (f a -> a) -> afunction without leaning onfixToMu? No matter what, it must involve using thef a -> aalgebra supplied as an argument to get anaresult. The direct route would be applying it to somef avalue. A major caveat is that, sinceais polymorphic, we must be able to conjure saidf avalue for any choice ofa. That is a feasible strategy as long asf-values happen to exist. In that case, we can do:To make the notation clearer, let's define a type for things we can use to define
forall a. (f a -> a) -> afunctions:Besides the direct route, there is just one other possibility. Given that
fis aFunctor, if we somehow have anf (Moo f)value we can apply the algebra twice, the first application being under the outerflayer, viafmapandfromMoo:Considering that we can also make
forall a. (f a -> a) -> aout off (Moo f)values, it makes sense to add them as a case ofMoo:Accordingly,
fromLayeredcan be incorporated tofromMoo:Note that, by doing so, we have sneakily moved from applying
algunder oneflayer to recursively applyingalgunder an arbitrary number offlayers.Next, we can note an
f Voidvalue can be injected into theLayeredconstructor:That means we don't actually need the
Emptyconstructor:What about the
Emptycase infromMoo? The only difference between the two cases is that, in theEmptycase, we haveabsurdinstead of\moo -> fromMoo moo alg. Since allVoid -> afunctions areabsurd, we don't need a separateEmptycase there either:A possible cosmetic tweak is flipping the
fromMooarguments, so that we don't need to write the argument tofmapas a lambda:Or, more pointfree:
At this point, a second look at our definitions suggests some renaming is in order:
And there it is: all
forall a. (f a -> a) -> afunctions have the form\alg -> cata alg tfor somet :: Fix f. Therefore,fixToMuis surjective, and we have the desired isomorphism.Addendum
In the comments, a germane question was raised about the applicability of the induction argument in the
cata Fix t = tderivation. At a minimum, the functor laws and parametricity ensure thatfmap (cata Fix)won't create extra work (for instance, it won't enlarge the structure, or introduce additional recursive positions to dig into), which justifies why stepping into the recursive positions is all that matters in the inductive step of the derivation. That being so, iftis a finite structure, the base case of an emptyf (Fix t)will eventually be reached, and all is clear. If we allowtto be infinite, however, we can keep descending endlessly,fmapafterfmapafterfmap, without ever reaching the base case.The situation with infinite structures, though, is not as awful as it might seem at first. Laziness, which is what makes infinite structures viable in the first place, allows us to consume infinite structures lazily:
While the succession of recursive positions extends infinitely, we can stop at any point and get useful results out of the surrounding
ListFfunctorial contexts. Such contexts, it bears repeating, are unaffected byfmap, and so any finite segment of the structure we might consume will be unaffected bycata Fix.This laziness reprieve reflects how, as mentioned elsewhere in this discussion, laziness collapses the distinction between the fixed points
Mu,FixandNu. Without laziness,Fixis not enough to encode productive corecursion, and so we have to switch toNu, the greatest fixed point. Here is a tiny demonstration of the difference: