Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catamorphisms for Church-encoded lists

I want to be able to use cata from recursion-schemes package for lists in Church encoding.

type ListC a = forall b. (a -> b -> b) -> b -> b

I used a second rank type for convenience, but I don't care. Feel free to add a newtype, use GADTs, etc. if you feel it is necessary.

The idea of Church encoding is widely known and simple:

three :: a -> a -> a -> List1 a 
three a b c = \cons nil -> cons a $ cons b $ cons c nil

Basically "abstract unspecified" cons and nil are used instead of "normal" constructors. I believe everything can be encoded this way (Maybe, trees, etc.).

It's easy to show that List1 is indeed isomorphic to normal lists:

toList :: List1 a -> [a]
toList f = f (:) []

fromList :: [a] -> List1 a
fromList l = \cons nil -> foldr cons nil l

So its base functor is the same as of lists, and it should be possible to implement project for it and use the machinery from recursion-schemes.

But I couldn't, so my question is "how do I do that?". For normal lists, I can just pattern match:

decons :: [a] -> ListF a [a]
decons [] = Nil
decons (x:xs) = Cons x xs

Since I cannot pattern-match on functions, I have to use a fold to deconstruct the list. I could write a fold-based project for normal lists:

decons2 :: [a] -> ListF a [a]
decons2 = foldr f Nil
  where f h Nil = Cons h []
        f h (Cons hh t) = Cons h $ hh : t

However I failed to adapt it for Church-encoded lists:

-- decons3 :: ListC a -> ListF a (ListC a)
decons3 ff = ff f Nil
  where f h Nil = Cons h $ \cons nil -> nil
        f h (Cons hh t) = Cons h $ \cons nil -> cons hh (t cons nil)

cata has the following signature:

cata :: Recursive t => (Base t a -> a) -> t -> a

To use it with my lists, I need:

  1. To declare the base functor type for the list using type family instance Base (ListC a) = ListF a
  2. To implement instance Recursive (List a) where project = ...

I fail at both steps.

like image 641
nponeccop Avatar asked Sep 28 '22 07:09

nponeccop


1 Answers

The newtype wrapper turned out to be the crucial step I missed. Here is the code along with a sample catamorphism from recursion-schemes.

{-# LANGUAGE LambdaCase, Rank2Types, TypeFamilies #-}

import Data.Functor.Foldable

newtype ListC a = ListC { foldListC :: forall b. (a -> b -> b) -> b -> b }

type instance Base (ListC a) = ListF a

cons :: a -> ListC a -> ListC a
cons x (ListC xs) = ListC $ \cons' nil' -> x `cons'` xs cons' nil'
nil :: ListC a
nil = ListC $ \cons' nil' -> nil'

toList :: ListC a -> [a]
toList f = foldListC f (:) []
fromList :: [a] -> ListC a
fromList l = foldr cons nil l

instance Recursive (ListC a) where
  project xs = foldListC xs f Nil
    where f x Nil = Cons x nil
          f x (Cons tx xs) = Cons x $ tx `cons` xs

len = cata $ \case Nil -> 0
                   Cons _ l -> l + 1
like image 193
nponeccop Avatar answered Oct 13 '22 11:10

nponeccop