N_P3Ch08a_Falg - rpeszek/notes-milewski-ctfp-hs GitHub Wiki

Markdown of literate Haskell program. Program source: /src/CTNotes/P3Ch08a_Falg.lhs

This note explores relationship between F-algebra and iso-recursion, Wadler's paper 'Recursive Types for Free', and provides examples of F-Algebras in A->B=>C category from N_P1Ch03b_FiniteCats.

Book Ref: CTFP P3 Ch8. F-Algebras.

 {-# LANGUAGE Rank2Types
  , ExistentialQuantification 
  #-}

 module CTNotes.P3Ch08a_Falg where

Fix/Unfix Fold/Unfold iso and equi-recusive types

According to the TAPL book all ML languages are iso-recursive. This means that the the language will use fold/unfold implicitly (as a part of operational semantics) or programs will be explicitly annotated with fold/unfold instructions.
Interesting quote: "In languages in the ML family, for example, every datatype definition implicitly introduces a recursive type." (TAPL book)

So if I type

data List a = Nil | Cons a (List a)

the compiler will change it to something like (quoted from TAPL):

NatList = μX. <nil:Unit, cons:{Nat,X}>;
-- operational semantics
unfold[μX.T] : μX.T -> [X →μX.T] T 
fold[μX.T] : [X →μX.T] T -> μX.T

-- unfolded form:
NLBody = <nil:Unit, cons:{Nat,NatList}>;

nil = fold [NatList] (<nil=unit> as NLBody); 
cons = λn:Nat.λl:NatList. fold [NatList] <cons={n,l}> as NLBody;

isnil = λl:NatList. case unfold [NatList] l of <nil=u>⇒true | <cons=p>⇒false; 
tail =λl:NatList. case unfold [NatList] l of <nil=u>⇒l | <cons=p>⇒p.2;
-- etc

Here is Haskell version of that transformation:

 newtype Fix f = Fix { unFix :: f (Fix f)}

 data ListF a x = Nil | Cons a x
 type ListFx a = Fix (ListF a)

 nil :: ListFx a
 nil = Fix Nil
 cons :: a -> ListFx a -> ListFx a
 cons a list = Fix (Cons a list)
 
 isnil :: ListFx a -> Bool
 isnil list = case unFix list of 
      Nil -> True
      Cons _ _ -> False
 tail' :: ListFx a  -> ListFx a 
 tail' list = case unFix list of
      Nil -> list
      Cons _ x -> x

Language operational semantics basically applies or removes a wrapping layer.

So iso-recursion works the same way as the Fix type! Two forces of programming: construction and deconstruction (patter matching) are represented as Fix and unFix (in TAPL terms 'fold' and unfold).

Long term TODO: TAPL implements equi-recursion using co-induction based on monotone functions.
It would be cool to understand how equi-recursion plays out using more CT approach. On high level things are very similar, we have the least and greatest fixpoints for example.

Recap
It is short of amazing that category theory aids in understanding of recursive types. Lambek theorem says that among F-algerbras F a -> a, if there is an initial one, its carrier type will be the fix point of F (F i ~= i). This is exactly the iso-recursive take on μ.

Interesting non-recursive approach. Existence of initial algebra.

Ref: Wadler, Recursive Types for Free

Considering type constructors F x containing x in positive position only (as it should for covariant functor), the least fixpoint of F can be expressed in System F (without recursion). Using Haskell this maps to:

 data LFix f = LFix (forall x. (f x -> x) -> x)

These functions

 fold:: forall x f. (f x -> x) -> LFix f -> x
 fold = \k (LFix t) -> t k

 in':: Functor f => f (LFix f) -> LFix f
 in' s = LFix $ \k -> k (fmap (fold k) s)

form commuting diagram

T = LFix F
	               in            
               F T ----------> T   
                |              |    
                |              |    
   F (fold X k) |              | fold X k
                |              |
                |              |
               F X ----------> X
	                k

T is carrier and in is evaluator of an F-algebra from which there is a map, fold X k, to every other F-algebra.
But is it really initial (is that map to any other object unique)? And it turns out that this uniqueness (the fact that (T,in) is initial) is a consequence of parametricity.
Using generic CT arguments, this is equivalent to following 2 conditions (using ref numbers 3 and 4 to match the paper):

             k                                 fold X k
      F X ----------> X                    T -----------> X
       |              |                    |              |
       |              |                    |              |
   F h |              | h    implies    id |              | h    (#3)
       |              |                    |              |
       |              |                    |              |
      F X' ---------> X'                   T -----------> X' .
             k'                               fold X' k'
--and 

fold T in  =  id.                                                (#4)

and, for System F, (#4) is automatic!

Quoting the paper: "Law (#3) does not follow from the reduction laws of polymorphic lambda calculus, and indeed there are models that do not satisfy it. But this law is satisfied in many models, including all those having the property of PARAMETRICITY ... 'Theorem for Free' derived from the type of 'fold' is just this law."

Finite Cats Example

Considering the A -> B => C example from N_P1Ch03b_FiniteCats. Since not trivial morphisms in this category are not invertible, Lambek theorem says that initial F-algerbra in this category (if exists) will need to always be based on id morphism.

Functor example

     MorphAB     MorphBCi (i=1,2)
  A   ---->   B  =====>  C
  |         /           /                    |
  |       /           /                      |
  |     /           /             functor F  |
  |   /           /                          |
  | /           /                           \ /
  A           B          C

F MorphAB = ID_A
F MorphBCi = MorphAB

To keep consistent with the book, in this section, I use (->) as morphism in A -> B => C, as if it was pseudo-defined like so:

type (->) = P1Ch03b_FiniteCats.HomeSet

(TODO makes sense to disambiguate)

Possible F-algebras (o, m) such that m :: F o -> o

(A, ID) - Initial
(B, MorphAB)
(C, MorphBC1)
(C, MorphBC2)

It is also interesting to note that for any functor F, F-algebra F A -> A is only possible if F A = A.
We have more choices for Fs that allow algebras F B -> B or F C -> C.

More about Recursive Types

See package recursion-schemes
See also https://stackoverflow.com/questions/45580858/what-is-the-difference-between-fix-mu-and-nu-in-ed-kmetts-recursion-scheme-pac

We have seen

newtype Fix f = Fix (f (Fix f))
data LFix f = LFix (forall x. (f x -> x) -> x)

There is also existentially formed greatest fixpoint

 data GFix f = forall a . GFix ((a -> f a) -> a)

all are supposed to be isomorphic in Haskell.

⚠️ **GitHub.com Fallback** ⚠️