Recursive Datatypes

Outline

  • What's Recursion?
  • Ex. A Recursive Program
  • Ex. Recursive Types
  • Folds 
  • Guided Exercises
    • Lists and Naturals
    • Syntax Trees
    • Labelled Trees
  • Recursive, AST, and Annotated
  • Applications of Recursive Datatypes

What's Recursion?

Properties of Recursion

  • Recursion occurs when a function or type is defined in terms of itself
  • Infinite recursion goes on forever
  • Non-infinite recursion terminates with a base case
    • A scenario where there is no self-reference
    • Steps must actually make progress towards base case in to terminate

Recursive Programs

An example

Sum of List - Iteratively

def sum(numbers: List[int]) -> int:
  result = 0

  for number in numbers:
    result = result + number

  return result
>>> sum([1,2,3])
6
>>> sum([10])
10
>>> sum([])
0

Sum of List - Recursively

def sum(numbers: List[int]) -> int:
  if (numbers == []):
    return 0
    
  head: int = numbers[0]
  tail: [int] = numbers[1:]

  return head + sum(tail)
>>> sum([1,2,3])
6
>>> sum([10])
10
>>> sum([])
0
def sum(numbers: List[int]) -> int:
  if (numbers == []):
    return 0
    
  head: int = numbers[0]
  tail: [int] = numbers[1:]

  return head + sum(tail)

Base Case

Self-reference/ recursive call

Sum of List - Recursively

sum :: [Int] -> Int
sum []   = 0
sum x:xs = x + (sum xs)

Sum of List - Recursively

Haskell edition

sum :: [Int] -> Int
sum []   = 0
sum x:xs = x + (sum xs)

Base Case

Self-reference/ recursive call

Sum of List - Recursively

Haskell edition

Recursive Types

An example

data Natural =
    One
  | Succ Natural

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

Lists and Naturals

data Natural =
    One
  | Succ Natural
  • Represent natural numbers (positive integers)

    • Example: (Python)

one: Nat = 1

# succ(nat: Nat) -> Nat
succ(one) == 2
succ(succ(one)) == 3

Naturals

data Natural =
    One
  | Succ Natural
natOne      = One
natTwo      = Succ One
natThree    = Succ (Succ One)
natFour     = Succ (Succ (Succ One))
alsoNatFour = Succ (natThree)
  • Represent natural numbers (positive integers)

    • Example: (Haskell)

Naturals

data List a =
    Empty
  | Cons a (List a)
  • This list is created by "cons-ing" onto the head of the list

    • Example: (Python)

num: int = 1
nums: List[int] = [2,3]
empty: List[Int] = []

# cons(head: int, tail: List[int]) -> List[int]
cons(num, empty) == [1]
cons(num, nums) == [1,2,3]
cons(num, cons(num, nums)) == [1,1,2,3]

Lists

data List a =
    Empty
  | Cons a (List a)
listOne         = Cons 1 Empty
listOneTwo      = Cons 1 (Cons 2 Empty)
listOneToThree  = Cons 1 (Cons 2 (Cons 3 Empty))
listOneToFour   = Cons 1 (Cons 2 (Cons 3 (Cons 4 Empty)))
listOneToFour'  = 1 : 2 : 4 : []
listOneToFour'' = [1, 2, 3, 4]
  • This list is created by "cons-ing" onto the head of the list

    • Example: (Haskell)

Lists

Folds

Folds (Python)

def concat_strings(strings: List[str]) -> str:
  return foldr_list(
    operation=lambda a, b: a + b,
    base_case="",
    elements=strings)

>>> concat_strings(["Man", " it's", " a", " hot", " one"])
"Man it's a hot one"
A = TypeVar('A'); B = TypeVar('B')
def foldr_list(operation: Callable[[A, B], B],
               base_case: B,
               elements: List[A]
               ) -> B:
  if (elements == []):
    return base_case
  head: A = elements[0]
  tail: List[A] = elements[1:]
  return operation(head, foldr_list(operation, base_case, tail))

Folds

-- Simplified right fold
foldrList :: (a -> b -> b) -> b -> [a] -> b
foldrList operation baseCase []     = baseCase
foldrList operation baseCase (x:xs) = 
  operation x (foldrList operation baseCase xs)
λ> concatStrings = foldrList (++) ""
λ> concatStrings ["Spanish", " Harlem", " Mona", " Lisa"]
"Spanish Harlem Mona Lisa"
λ> concatStrings []
""
sum :: [Int] -> Int
sum numbers = foldr (+) 0 numbers
λ> sum [1,2,3]
6
λ> sum [10]
10
λ> sum []
0

Sum - As a fold

sum :: [Int] -> Int
sum numbers = foldr (+) 0 numbers

Base Case

Self-reference/ recursive call

Sum - As a fold

Visualizing Folds

foldrList :: (a -> b -> b) -> b -> [a] -> b
 foldrList f baseCase [x1, x2, x3]
             =
----------------------------------
    :        ->     f
   / \             / \
  x1  :      ->   x1  f 
     / \             / \
    x2  :    ->     x2  f
       / \             / \
      x3 []  ->       x3  baseCase
sum :: [Int] -> Int
sum numbers = foldr (+) 0 numbers

Visualizing Sum

      sum [1, 2, 3]
           =
------------------------
  :        ->     +
 / \             / \
1   :      ->   1   + 
   / \             / \
  2   :    ->     2   +
     / \             / \
    3  []  ->       3   0

Guided Exercises

  • Goal: Generalize similar recursive types into one most excellent and general recursive type

Strategies

  • Find a way to visualize values and patterns
  • Identify similar recursive datatypes and factor out similarities
    • If we only have one type, mess with it until we have another!
    • Try formulating in terms of primitive ADTs
      • Tuple, Maybe, Either, Identity, Void, Unit
  • Turn concrete types into polymorphic ones
  • Call new patterns by new names

Generalizing

Lists and Naturals

data Natural =
    One
  | Succ Natural

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

Visualize?

data Natural =
    One
  | Succ Natural

Visualizing  Naturals

  |                                     = One
 / \
One Succ (|)                            = Succ One
         / \
       One  Succ (|)                    = Succ (Succ One)
                 / \
               One  Succ (|)            = Succ (Succ (Succ One))
                         / \
                       One  Succ (|)    = Succ (Succ (Succ (Succ One)))
                                 / \
                               One  ... = Succ (Succ (Succ (Succ ...)))

Visualizing Lists

  |                             = Empty
 / \
E   Cons 
    1 (|)                       = Cons 1 Empty
      / \
     E   Cons
         2 (|)                  = Cons 1 (Cons 2 Empty)
           / \
          E   Cons
              3 (|)             = Cons 1 (Cons 2 (Cons 3 Empty))
                / \
               E   Cons
                   4 (|)        = Cons 1 (Cons 2 (Cons 3 (Cons 4 Empty)))
                     / \
                    E   Cons
                        5 (...) = Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 ...))))
                               
data List a =
    Empty
  | Cons a (List a)
data Natural =
    One
  | Succ Natural

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

What's similar?

data Natural =
    One
  | Succ Natural

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

Both have a "unit" value

data Natural =
    One
  | Succ Natural

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

Both have a recursive value

data Natural =
    One
  | Succ Natural

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

Generalized?

data Recursive a =
    Unit
  | Ctor (Recursive a)

?

Recursive can either be a "unit" value or a recursively defined value

Generalize?

?

data Recursive a =
    Unit
data Natural =
    One

data List a  =
    Empty

Isomorphic?

?=

one    = One   :: Natural
empty  = Empty :: List Int
rOne   = Unit :: Recursive ()
rEmpty = Unit :: Recursive Int

?

data Recursive a =
    Unit
data Natural =
    One

data List a  =
    Empty

Yes!

one    = One   :: Natural
empty  = Empty :: List Int
rOne   = Unit :: Recursive ()
rEmpty = Unit :: Recursive Int
data Recursive a =
    Unit
data Natural =
    One

data List a  =
    Empty

==

data Natural =
  Succ Natural

data List a  =
  Cons a (List a)

Isomorphic?

data Recursive a =
  Ctor (Recursive a)

?

Isomorphic?

rplusOne r = Ctor r :: Recursive ()
rconsOne r = Ctor r :: Recursive Int

rplusTwo    r = Ctor (Ctor r)
rconsOneTwo r = Ctor (Ctor r)
plusOne n = Succ   n :: Natural
consOne t = Cons 1 t :: List Int

plusTwo    n = Succ   (Succ   n)
consOneTwo t = Cons 1 (Cons 2 t)

?=

data Natural =
  Succ Natural

data List a  =
  Cons a (List a)
data Recursive a =
  Ctor (Recursive a)

?

NO!

rplusOne r = Ctor r :: Recursive ()
rconsOne r = Ctor r :: Recursive Int
                ^?^
              
rplusTwo    r = Ctor (Ctor r)
rconsOneTwo r = Ctor (Ctor r)
                   ^?^   ^?^
plusOne n = Succ  n :: Natural
consOne t = Cons 1 t :: List Int
                 ^

plusTwo    n = Succ   (Succ   n)
consOneTwo t = Cons 1 (Cons 2 t)
                    ^       ^

!=

data Natural =
  Succ Natural

data List a  =
  Cons a (List a)
data Recursive a =
  Ctor (Recursive a)

?

Maybe Label?

data Recursive a =
    Unit
  | Ctor (Maybe a) (Recursive a)

?

data Natural =
    One
  | Succ Natural

data List a =
    Empty
  | Cons a (List a)
data Recursive a =
    Unit
  | Ctor (Maybe a) (Recursive a)
type Nat = Recursive ()

goodOne   = Unit
goodTwo   = Ctor Nothing Unit
goodThree = Ctor Nothing (Ctor Nothing Unit)

Maybe Label?

?

Can we make a Natural?

data Recursive a =
    Unit
  | Ctor (Maybe a) (Recursive a)
type Nat = Recursive ()

goodOne   = Unit
--        = One
goodTwo   = Ctor Nothing Unit
--        = Succ         One
goodThree = Ctor Nothing (Ctor Nothing Unit)
--        = Succ         (Succ         One)

Maybe Label?

Can we make a Natural?

YES

data Recursive a =
    Unit
  | Ctor (Maybe a) (Recursive a)
type List a = Recursive a

goodEmpty  = Unit
goodOne    = Ctor (Just 1) Unit
goodOneTwo = Ctor (Just 1) (Ctor (Just 2) Unit)

Maybe Label?

?

Can we make a List?

data Recursive a =
    Unit
  | Ctor (Maybe a) (Recursive a)
type List a = Recursive a

goodEmpty  = Unit
--         = Empty
goodOne    = Ctor (Just 1) Unit
--         = Cons       1  Empty
goodOneTwo = Ctor (Just 1) (Ctor (Just 2) Unit)
--         = Cons       1  (Cons       2  Empty)

Maybe Label?

Can we make a List?

YES

data Recursive a =
    Unit
  | Ctor (Maybe a) (Recursive a)

Maybe Label?

So we can make a natural and list!

But something is wrong...

type Nat = Recursive (Recursive Int)

badNat :: Nat
badNat  = Ctor (Ctor (Just 666) Unit) Unit
type IntList = Recursive Int

badList :: IntList
badList = Ctor Nothing (Ctor (Just 1) Unit)

badEmpty :: IntList
badEmpty = Ctor Nothing (Ctor Nothing Unit)

🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥

data Natural =
    One
  | Succ Natural

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

Wrapper on Nodes?

data Recursive w a =
    Unit
  | Ctor (w (Recursive w a))

?

-- |List wrapper w/ Label!
data ListNode a r = ListNode
  { nodeValue :: a             
  , tailNodes :: r 
  }

-- |Nat wrapper w/o Label!
data NatNode r = NatNode
  { tailNodes :: r  
  }

type List a = Recursive (ListNode a) a
type Nat    = Recursive NatNode ()
data Recursive w a =
    Unit
  | Ctor (w (Recursive w a))

?

type List a = Recursive (ListNode a) a
type Nat    = Recursive NatNode ()

oneTwo = Ctor (ListNode 1 (Ctor (ListNode 2 Unit)))


two    = Ctor (NatNode    (Ctor (NatNode    Unit)))
data Recursive w a =
    Unit
  | Ctor (w (Recursive w a))

?

type List a = Recursive (ListNode a) a
type Nat    = Recursive NatNode ()

oneTwo = Ctor (ListNode 1 (Ctor (ListNode 2 Unit)))
--       Cons           1 (Cons           2 Unit)

two    = Ctor (NatNode    (Ctor (NatNode    Unit))) 
--       Succ             (Succ             Unit)  
data Recursive w a =
    Unit
  | Ctor (w (Recursive w a))

Mission COMPLETE!!

data Recursive w a =
   Unit
 | Ctor (w (Recursive w a))

Or can we go further?

data Recursive w a =
   Unit
 | Ctor (w (Recursive w a))

Or can we go further?

  • Hint
    • Recursive can be either a unit or a parameterized value
    • Parameterized value is parameterized by a wrapper

Don't we already have a wrapper type that can be unit or a parameterized value?

Don't we already have a wrapper type that can be unit or a parameterized value?

data Maybe a =
   Nothing
 | Just a 
data ListNode a r = ListNode
  { nodeValue :: a
  , tailNodes :: Maybe r
  }

data NatNode r = NatNode
  { tailNodes :: Maybe r 
  }

type List a = Recursive (ListNode a)
type Nat    = Recursive NatNode
data Recursive w =
    Ctor (w (Recursive w))

?

data NatNode r = NatNode
  { tailNodes :: Maybe r 
  }
type Nat = Recursive NatNode

one :: Nat
one = Ctor (NatNode Nothing)

succ :: Nat -> Nat
succ = Ctor . NatNode . Just
natOne      = one
natTwo      = succ one
natThree    = succ (succ one)
natFour     = succ (succ (succ one))
alsoNatFour = succ (natThree)
data Recursive w = Ctor (w (Recursive w))
data ListNode a r = ListNode
  { nodeValue   :: a
  , tailNodePtr :: Maybe r
  }

type List a = Recursive (ListNode a)

empty :: List Int
empty = Ctor (ListNode 10 Nothing)

cons :: a -> List a -> List a
cons a = Ctor . ListNode a . Just
listOne         = cons 1 empty
listOneTwo      = cons 1 (cons 2 empty)
listOneToThree  = cons 1 (cons 2 (cons 3 empty))
listOneToFour   = cons 1 (cons 2 (cons 3 (cons 4 empty)))
data Recursive w = Ctor (w (Recursive w))

?

data ListNode a r = ListNode
  { nodeValue   :: a
  , tailNodePtr :: Maybe r
  }

type List a = Recursive (ListNode a)

empty :: List Int
empty = Ctor (ListNode 10 Nothing)
                       ^

cons :: a -> List a -> List a
cons a = Ctor . ListNode a . Just
data Recursive w = Ctor (w (Recursive w))

That's not empty!

What if we flip the wrapper?

data ListNode a r = ListNode
  { nodeValue   :: a
  , tailNodePtr :: r
  }

type List a = Recursive (Maybe (ListNode a))

empty :: List a
empty = Ctor . Nothing

cons :: a -> List a -> List a
cons a = Ctor . ListNode a . Just
listOne         = cons 1 empty
listOneTwo      = cons 1 (cons 2 empty)
listOneToThree  = cons 1 (cons 2 (cons 3 empty))
listOneToFour   = cons 1 (cons 2 (cons 3 (cons 4 empty)))
data Recursive w = Ctor (w (Recursive w))

CAN WE GO DEEPER???

data NatF r =
    OneF
  | SuccF r

type Nat = Recursive NatF

one :: Nat
one = Ctor OneF

succ :: Nat -> Nat
succ = Ctor . SuccF
natOne      = one
natTwo      = succ one
natThree    = succ (succ one)
natFour     = succ (succ (succ one))
alsoNatFour = succ (natThree)
data Recursive w = Ctor (w (Recursive w))
data Natural =
    One
  | Succ Natural

Visualizing Naturals

  |                                     = One
 / \
One Succ (|)                            = Succ One
         / \
       One  Succ (|)                    = Succ (Succ One)
                 / \
               One  Succ (|)            = Succ (Succ (Succ One))
                         / \
                       One  Succ (|)    = Succ (Succ (Succ (Succ One)))
                                 / \
                               One  ... = Succ (Succ (Succ (Succ ...)))
  Ctor                                   = Ctor OneF
  / \
OneF SuccF (Ctor)                        = Ctor (SuccF (Ctor OneF))
            / \
         OneF  SuccF (Ctor)              = Ctor (SuccF (Ctor (SuccF (Ctor OneF
                     / \                     ))))
                  OneF  SuccF (Ctor)     = Ctor (SuccF (Ctor (SuccF (Ctor (SuccF
                         / \                 (Ctor OneF)))))
                      OneF  SuccF (Ctor) = Ctor (SuccF (Ctor (SuccF (Ctor (SuccF
                                 / \         (Ctor (SuccF (Ctor OneF))))))
                              OneF  ...  = Ctor (SuccF (Ctor (SuccF (Ctor (SuccF
                                              (Ctor (SuccF (Ctor (SuccF
                                                (Ctor OneF)))))))  
data Recursive w = Ctor (w (Recursive w))
data NatF r =
    OneF
  | SuccF r

type Nat = Recursive NatF

one :: Nat
one = Ctor OneF

succ :: Nat -> Nat
succ num = Ctor (SuccF num)
data ListF a r =
    EmptyF
  | ConsF a r

type List = Recursive (ListF a)

empty :: List
empty = Ctor EmptyF

cons :: a -> List -> List
cons = Ctor . ConsF
listOne         = cons 1 empty
listOneTwo      = cons 1 (cons 2 empty)
listOneToThree  = cons 1 (cons 2 (cons 3 empty))
listOneToFour   = cons 1 (cons 2 (cons 3 (cons 4 empty)))
data Recursive w = Ctor (w (Recursive w))

Visualizing  Lists

  |                             = Empty
 / \
E   Cons 
    1 (|)                       = Cons 1 Empty
      / \
     E   Cons
         2 (|)                  = Cons 1 (Cons 2 Empty)
           / \
          E   Cons
              3 (|)             = Cons 1 (Cons 2 (Cons 3 Empty))
                / \
               E   Cons
                   4 (|)        = Cons 1 (Cons 2 (Cons 3 (Cons 4 Empty)))
                     / \
                    E   Cons
                        5 (...) = Cons 1 (Cons 2 (Cons 3 (Cons 4 (Cons 5 ...))))
                               
data List a =
    Empty
  | Cons a (List a)
data ListF a r =
    EmptyF
  | ConsF a r

type List = Recursive (ListF a)

empty :: List
empty = Ctor EmptyF

cons :: a -> List -> List
cons head tail =
  Ctor (ConsF head tail)
data Recursive w = Ctor (w (Recursive w))
  Ctor                              = Ctor EmptyF
  / \
EmpF ConsF
    1 (Ctor)                        = Ctor (ConsF 1 (Ctor EmptyF))
        / \
     EmpF  ConsF
           2 (Ctor)                 = Ctor (ConsF 1 (Ctor (ConsF 2 (Ctor EmptyF
               / \                      ))))
            EmpF  ConsF
                  3 (Ctor)          = Ctor (ConsF 1 (Ctor (ConsF 2 (Ctor (ConsF 3
                      / \               (Ctor EmptyF)))))
                   EmpF  ConsF
                         4 (Ctor)   = Ctor (ConsF 1 (Ctor (ConsF 2 (Ctor (ConsF 3
                             / \        (Ctor (ConsF 4 (Ctor EmptyF))))))
                          EmpF  ... = Ctor (ConsF 1 (Ctor (ConsF 2 (Ctor (ConsF 3
                                        (Ctor (ConsF 4 (Ctor (ConsF ...

Takeaways

  • Two ways of using Recursive:
    • Compose Different Wrappers
      • Different wrappers give us different powers
        • Maybe
          • Terminates recursion
        • Tuple
          • Labelled layers/nodes of recursion
        • Identity
          • Infinite recursion

Takeaways

  • Two ways of using Recursive:
    • Pattern Functor
      • We can our own wrapper type with terms that make sense to us
      • Steps:
        1. Lift original type (parameterize)
        2. Replace recursive value with continuation

Takeaways

  • Recursive captures a pattern of recursion we can use to make recursive types
    • Like the fold function let's us have recursive function without using explicit recursion, Recursive let's us have recursive type with using explicit recursion

Generalizing

Leaf Valued Trees

Trees

with values at leaves

  • Let's look at a tree that holds values at the leaves:
  • Leaves (values)
  • Branches
  • Recursion
  • Leaves (values)
  • Branches
    • Exactly 2
  • Recursion
data Tree
  = Leaf Int
  | Branch Tree Tree

Binary Trees

with values at leaves

data Tree
  = Leaf Int
  | Branch Tree Tree
someTree :: Tree
someTree =
  Branch 
    (Branch 
      (Branch (Leaf 1) (Leaf 2))
      (Branch (Leaf 3) (Leaf 4))
    )  
    (Leaf 5)


 --             •
 --            / \
 --          •    \
 --         / \    \
 --       •     •   \
 --      / \   / \   \
 --     1   2 3   4   5
someValues = (1,2,3,4,5)
someTree :: Tree
someTree =
  Branch 
    (Branch 
      (Branch (Leaf 1) (Leaf 2))
      (Branch (Leaf 3) (Leaf 4))
    )  
    (Leaf 5)


 --             •
 --            / \
 --          •    \
 --         / \    \
 --       •     •   \
 --      / \   / \   \
 --     1   2 3   4   5
someTree :: Tree
someTree =
  Branch 
    (Branch 
      (Branch (Leaf 1) (Leaf 2))
      (Branch (Leaf 3) (Leaf 4))
    )  
    (Leaf 5)


 --             •
 --            / \
 --          •    \
 --         / \    \
 --       •     •   \
 --      / \   / \   \
 --     1   2 3   4   5
someStructuredValues = (((1, 2), (3, 4)), 5)
someList = (1, 2, 3, 4, 5)
 --             •
 --            / \
 --          •    \
 --         / \    \
 --       •     •   \
 --      / \   / \   \
 --     1   2 3   4   5
 --            (,)
 --            / \
 --         (,)   \
 --         / \    \
 --      (,)   (,)  \
 --      / \   / \   \
 --     1   2 3   4   5
someStructuredValues = (((1, 2), (3, 4)), 5)
 --             +
 --            / \
 --          -    \
 --         / \    \
 --       *     +   \
 --      / \   / \   \
 --     1   2 3   4   5
someExpression = ((1 * 2) - (3 + 4)) + 5
-- Arithmetic Expressions
data Math
  = Lit Int
  | Add Math Math
  | Sub Math Math
  | Mul Math Math
someMath :: Math
someMath =
  Add 
    (Sub 
      (Mul (Lit 1) (Lit 2))
      (Add (Lit 3) (Lit 4))
    )  
    (Lit 5)

-- ((1 * 2) - (3 + 4)) + 5
--             +
--            / \
--          -    \
--         / \    \
--       *     +   \
--      / \   / \   \
--     1   2 3   4   5
data Tree
  = Leaf Int
  | Branch Tree Tree
data Math
  = Lit Int
  | Add Math Math
  | Sub Math Math
  | Mul Math Math
  | Neg Math
data Tree a
  = Leaf a
  | Branch (Tree a) (Tree a)
data Math a
  = Lit a
  | Add (Math a) (Math a)
  | Sub (Math a) (Math a)
  | Mul (Math a) (Math a)
  | Neg (Math a)
data Tree a
  = Leaf a
  | Branch (Tree a) (Tree a)
data Math a
  = Lit a
  | Add (Math a) (Math a)
  | Sub (Math a) (Math a)
  | Mul (Math a) (Math a)
  | Neg (Math a)

Both have a leaf element

data Tree a
  = Leaf a
  | Branch (Tree a) (Tree a)
data Math a
  = Lit a
  | Add (Math a) (Math a)
  | Sub (Math a) (Math a)
  | Mul (Math a) (Math a)
  | Neg (Math a)

Both have a branches of

sub-trees/sub-expressions

data Tree a
  = Leaf a
  | Branch (Pair a)

data Pair a = Pair (Tree a) (Tree a)
data Math a
  = Lit a
  | Op (Operator a)

data Operator a
  = Add (Math a) (Math a)
  | Sub (Math a) (Math a)
  | Mul (Math a) (Math a)
  | Neg (Math a)
data Tree a
  = Leaf a
  | Branch (Pair (Tree a))

data Pair a = Pair a a
data Math a
  = Lit a
  | Op (Operator (Math a))

data Operator a
  = Add a a
  | Sub a a
  | Mul a a
  | Neg a
data Tree a
  = Leaf a
  | Branch (Pair (Tree a))

data Pair a = Pair a a
data Math a
  = Lit a
  | Op (Operator (Math a))

data Operator a
  = Add a a
  | Sub a a
  | Mul a a
  | Neg a
data Tree a
  = Leaf a
  | Branch (Pair (Tree a))

data Pair a = Pair a a
data Math a
  = Lit a
  | Op (Operator (Math a))

data Operator a
  = Add a a
  | Sub a a
  | Mul a a
  | Neg a
type Tree = AST Pair Int

data Pair a = Pair a a
data Math = AST Operator Int

data Operator a
  = Add a a
  | Sub a a
  | Mul a a
  | Neg a
data AST op a
  = Leaf a
  | Branch (op (AST op a))

Generic Abstract Syntax Tree

data Math
  = Lit Int
  | Add Math Math
  | Sub Math Math
  | Mul Math Math
someMath :: Math
someMath =
  Add 
    (Sub 
      (Mul (Lit 1) (Lit 2))
      (Add (Lit 3) (Lit 4))
    )  
    (Lit 5)
someAstMath :: Math
someAstMath =
  Branch
    (Add 
      (Branch
        (Sub
          (Branch
            (Mul (Leaf 1) (Leaf 2))
          )
          (Branch
            (Add (Leaf 3) (Leaf 4))
          )
        )
      )
      (Leaf 5)
    )
type Math = AST Operator Int

data Operator a
  = Add a a
  | Sub a a
  | Mul a a

Old

New

data Math
  = Lit Int
  | Add Math Math
  | Sub Math Math
  | Mul Math Math
someMath :: Math
someMath =
  Add 
    (Sub 
      (Mul (Lit 1) (Lit 2))
      (Add (Lit 3) (Lit 4))
    )  
    (Lit 5)
type Math = AST Operator Int

data Operator a
  = Add a a
  | Sub a a
  | Mul a a

Old

New

someAstMath :: Math
someAstMath =
  Branch
    (Add 
      (Branch
        (Sub
          (Branch
            (Mul (Leaf 1) (Leaf 2))
          )
          (Branch
            (Add (Leaf 3) (Leaf 4))
          )
        )
      )
      (Leaf 5)
    )

Simple Language

An Example

let addOne = (\a -> a + 1)
in  addOne 42
addOne = lambda a: a + 1
addOne(42)

Haskell

Python

  • Let's make a simple language that can express the following:
type SimpleLang = AST Operation Term

newtype Identifier = String

data Operation a
  = LetIn Identifier a a  -- Variable Binding
  | Function Identifier a -- Function Abstraction
  | Apply a a             -- Apply Two Expressions

data Term
  = Var Identifier
  | Num Number

Simple ASTs for Simple Langs

let addOne = (\a -> a + 1)
in  addOne 42
addOne = lambda a: a + 1
addOne(42)

Haskell

Python

addOne :: SimpleLang
addOne =
  (Branch
    (LetIn "addOne"
       (Branch
         (Function "a"
           (Apply
             (Branch
               (Apply
                 (Leaf (Var "+"))
                 (Leaf (Var "a"))))
             (Leaf (Num 1))))))
       (Branch
         (Apply
           (Leaf (Var "addOne"))
           (Leaf (Num 42))))))

Generalizing

Labelled Trees

data Rose a

Rose Tree

with values at all nodes

  • A rose tree is an n-ary tree

    • Each node has zero or more children, all of which are themselves rose trees

      • Unbounded number of branches

      • Value at every node (including branches)

type List a = [a]

data Rose a
  = Leaf a
  | Branch (List (Rose a))
[ Branch 
  [ Branch 
    [ Branch
      [ Leaf 1
      , Leaf 2
      ]
    , Branch
      [ Leaf 3
      , Leaf 4
      ]
  ]  
  , Leaf 5
  , Leaf 6
  , Leaf 7
]


--            [ ]---
--            / \ \ \
--         [ ]   \ \ \
--         / \    \ \ \
--      [ ]   [ ]  \ \ \
--      / \   / \   \ \ \
--     1   2 3   4   5 6 7
[ [ [1,2]
  , [3,4]
  ]
, 5
, 6
, 7
]
type List a = [a]

data Rose a
  = Leaf a
  | Branch (List (Rose a))
[ Branch 
  [ Branch 
    [ Branch
      [ Leaf 1
      , Leaf 2
      ]
    , Branch
      [ Leaf 3
      , Leaf 4
      ]
  ]  
  , Leaf 5
  , Leaf 6
  , Leaf 7
]


--             ?----
--            / \ \ \
--          ?    \ \ \
--         / \    \ \ \
--       ?     ?   \ \ \
--      / \   / \   \ \ \
--     1   2 3   4   5 6 7
  • But we said Rose Trees should be labelled at all nodes?
type List a = [a]

data Rose a
  = Leaf a
  | Branch ? (List (Rose a))
[ Branch ?
  [ Branch ?
    [ Branch ?
      [ Leaf 1
      , Leaf 2
      ]
    , Branch ?
      [ Leaf 3
      , Leaf 4
      ]
  ]  
  , Leaf 5
  , Leaf 6
  , Leaf 7
]

--             ?----
--          [ / \ \ \ ]
--          ?    \ \ \
--       [ / \ ]  \ \ \
--       ?     ?   \ \ \
--     [/ \] [/ \]  \ \ \
--     1   2 3   4   5 6 7
( ?
, [( ?
   , [ (?, [1,2])
     , (?, [3,4])
     ]
  , 5
  , 6
  , 7
  ]
)
type List a = [a]

data Rose a
  = Leaf a
  | Branch a (List (Rose a))
[ Branch 200
  [ Branch 300
    [ Branch 400
      [ Leaf 1
      , Leaf 2
      ]
    , Branch 500
      [ Leaf 3
      , Leaf 4
      ]
  ]  
  , Leaf 5
  , Leaf 6
  , Leaf 7
]


--            200---
--          [ / \ \ \ ]
--         300   \ \ \
--       [ / \ ]  \ \ \
--      400   500  \ \ \
--     [/ \] [/ \]  \ \ \
--     1   2 3   4   5 6 7
( 200
, [ (300
    , [(400, [1, 2])
      ,(500, [3, 4])
      ]
    )
  , 5
  , 6
  , 7
  ]
)
type List a = [a]

data Rose a
  = Leaf a
  | Branch a (List (Rose a))
[ Branch 200
  [ Branch 300
    [ Branch 400
      [ Leaf 1
      , Leaf 2
      ]
    , Branch 500
      [ Leaf 3
      , Leaf 4
      ]
  ]  
  , Leaf 5
  , Leaf 6
  , Leaf 7
]


--            200---
--          [ / \ \ \ ]
--         300   \ \ \
--       [ / \ ]  \ \ \
--      400   500  \ \ \
--     [/ \] [/ \]  \ \ \
--     1   2 3   4   5 6 7
  • What's the difference?
    • Labelled Leaf
    • Labelled branch with an empty subtree
type List a = [a]

data Rose a
  = Leaf a
  | Branch a (List (Rose a))
( 200
, [ (300
    , [ (400, [ 1, 2])
      , (500, [ 3, 4])
      ]
    )
  , 5
  , 6
  , 7
  ]
)
[ Branch 200
  [ Branch 300
    [ Branch 400
      [ Leaf 1
      , Leaf 2
      ]
    , Branch 500
      [ Leaf 3
      , Leaf 4
      ]
  ]  
  , Leaf 5
  , Leaf 6
  , Leaf 7
]


--            200---
--          [ / \ \ \ ]
--         300   \ \ \
--       [ / \ ]  \ \ \
--      400   500  \ \ \
--     [/ \] [/ \]  \ \ \
--     1   2 3   4   5 6 7
type List a = [a]

data Rose a
  = Branch a (List (Rose a))
( 200
, [ ( 300
    , [ (400, [ (1, [])
              , (2, [])
              ]
        )
      , (500, [ (3, [])
              , (4, [])
              ]
        )
      ]
    )
    , (5, [])
    , (6, [])
    , (7, [])
  ]
)
[ Branch 200
  [ Branch 300
    [ Branch 400
      [ Branch 1 []
      , Branch 2 []
      ]
    , Branch 500
      [ Branch 3 []
      , Branch 4 []
      ]
  ]  
  , Branch 5 []
  , Branch 6 []
  , Branch 7 []
]


--            200---
--          [ / \ \ \ ]
--         300   \ \ \
--       [ / \ ]  \ \ \
--      400   500  \ \ \
--     [/ \] [/ \]  \ \ \
--     1   2 3   4   5 6 7
--    []  [] [] []  [] [] []

Generalized Annotated Trees

-- Annotated
data Annotated f a
  = Ann a (f (Annotated f a))

-- equivalent to above
data Annotated' f a = Annotated'
  { annotation :: a
  , annSubTree :: f (Annotated' f a)
  }

Generalized Annotated Trees

type RoseTree a       = Annotated [] a
type Trie a           = Annotated (StrMap) a
type List a           = Annotated (Identity) a
type DecisionTree a r = Annotated ((->) r) a
type BST a            = Annotated (Compose Pair Maybe) a
type MerkleTree a     = Annotated (HashTrie) a

Holy Toledo! So Versatile!

Fix, Free, and Cofree

Reviewing our Findings

  • Shape of recursive data types
    • Recursive
    • AST
    • Annotated
data AST f a
  = Leaf a
  | Branch (f (Tree a))

data Annotated f a
  = Annotated a (f (Annotated f a))

-- equivalent to `Annotated`
data Annotated2 f a = Annotated2
  { annotation :: a
  , annSubTree :: f (Annotated2 f a)
  }

AST

  • A value OR a branch

Annotated

  • A value AND a branch
-- Free
data Free f a
  = Pure a
  | Rollup (f (Free a))


-- Cofree
data Cofree f a
  = Cofree a (f (Cofree f a))

-- equivalent to `Cofree`
data Cofree2 f a = Cofree2
  { annotation :: a
  , annSubTree :: f (Cofree2 f a)
  }

Free

  • A value OR a branch

Cofree

  • A value AND a branch
data Free f a
  = Pure a
  | Rollup (f (Free a))

data Cofree f a
  = a <: (f (Cofree f a))

Free

  • A value OR a branch

Cofree

  • A value AND a branch

Recursive

data Recursive w = Ctor (w (Recursive w))

Fix

newtype Fix f
  = Fix (f (Fix f))

A fixed point (or fixpoint) is a point that, when provided to a function, yields as a result that same point.

  • A fixed point of a function/type is a point that won't change under repeated application of the function/type.
newtype Fix f
  = Fix (f (Fix f))

data Free f a
  = Pure a
  | Rollup (f (Free a))

data Cofree f a
  = a <: (f (Cofree f a))

Fix, Free, and Cofree

Applications and Further Reading

Specification and Interpretation

with Free DSLs

Recursion Schemes

Extensible Effects

Freer-Simple

Fused Effects

Functor Combinators

Functor Combinators

  • Fix, Free, and CoFree generalize recursion with datatypes that wrap every layer of recursion with some constructor
    • Enables generic operations that know what to do with those constructors
freeMath :: Fix MathF
freeMath =
  Free
    (AddF 
      (Free
        (SubF
          (Free
            (MulF
                (Pure
                  (1))
                (Pure
                  (2))))
          (Free
            (AddF
                (Pure
                  (3))
                (Pure 
                  (4)))))
      (Pure
        (5)))
fixList :: Fix ListF
fixList =
  Fix
    (ConsF 10 
      (Fix
        (ConsF 10
          (Fix
            (ConsF 10
              (Fix
                (NilF))))))))
                

Functor Combinators

  • But can we generalize behaviors of our "Wrapper" type variable f to do more?
    • Yes!
  • Generalize your use of functors composition!
    • Constrain with functor-combinators!

Functor Combinators

  • Functor Sums
    • Higher-order Either

    • Provide either case, and user has to handle both possibilities

data (f :+: g) a
    = L1 (f a)
    | R1 (g a)

type MbNatural      = Fix Maybe
type SumNatural f g = Fix (Identity :+: Proxy)

type List a = Fix ((,) a :+: Proxy)
mbThree  = Fix . Just          . Fix . Just          $ Fix Nothing    :: MbNat
sumThree = Fix . L1 . Identity . Fix . L1 . Identity . Fix $ R1 Proxy :: SumNat

oneTwoThree = Fix . L1 . (,) 1 . Fix . L1 . (,) 2 
  . Fix . L1 . (,) 3 . Fix $ R1 Proxy :: List Int -- ~= [1,2,3]

Functor Combinators

  • Functor Products
    • Higher-order Tuple

    • Pairs wrappers, and let's user can choose which one they want to use

data (f :*: g) a = f a :*: g a

References

Recursive Datatypes

By Heneli Kailahi

Recursive Datatypes

Explores basic recursive types in Haskell

  • 263