Homework 7

Recursive types and algebraic data types

This homework is written in literate Haskell; you can download the raw source to fill in yourself. You’re welcome to submit literate Haskell yourself, or to start fresh in a new file, literate or not.

Please submit homeworks via the DCI submission page.

We saw briefly in class the correspondence between datatype notation and recursive/algebraic type notation.

Let’s translate a Haskell datatype to algebraic types. We have the definition:

data IntList =
  | Cons Int IntList

An IntList is either:

  • Nil, or
  • Cons n l, such that n is an Int and l is an IntList.

In algebraic types, we interpret ‘either’ using sums (written + in math and Either in Haskell). So in pseudo-Haskell, we have:

IntList = Nil + Cons Int IntList

Next, we don’t use constructor names in algebraic datatypes—we just represent the data. So Nil and Cons need to be replaced. Now, Nil has no arguments—the only information Nil provides is that it isn’t Cons. We can represent it using (), the unit type.

IntList = () + Cons Int IntList

Cons, on the other hand, has some arguments. We’ll represent them as a pair, using * (or × on paper, or (,) in Haskell).

IntList = () + (Int,IntList)

At this point, the only left to do is to get rid of the recursion. To do this, we use μ. The process is mechanical: if you have a definition of the form X = [some type t in terms of X], you translate it to X = μ α. t[α/X]. Here, that gives us:

X = mu alpha. () + (Int,alpha)

In general, translating to datatypes is a mechanical process. You’ll do some by hand in problem 1, but then you’ll make it automatic in problem 2.

Problem 1: understanding recursive and algebraic types

(a) 15 points

Translate the following datatype definitions to (possibly recursive) algebraic datatypes. You don’t need to show your work, but please format your answer like the demonstration above.

data Answer = Yes | No | Maybe

  fill in here

data MaybeInt = Nothing | Maybe Int

data Rope = Leaf String | Node Rope Int Rope

(b) 15 points

Translate the following algebraic datatypes to Haskell-style datatype definitions. You’ll have to come up with constructor names… what do you think they should be named? What would you name these types?

() + ()

  fill in here

mu alpha. Int + (alpha,alpha)

  fill in here

mu alpha. () + (Int,mu beta. () + (alpha,beta))

  fill in here

(c) 10 points

Consider the type μα. α. What kinds of values inhabit that type?

What about the type μα. α→α—what kinds of values inhabit that type?

HINT: One way to approach this kind of problem is to try to produce a typing derivation. Note that every type comes with terms that introduce values—-> comes with lambda, (,) comes with pairs, (+) comes with left and right—and terms that use, or eliminate, values—-> comes with application, (,) comes with fst and snd, and (+) comes with case analysis.

The corresponding introduction and elimination forms for μ types are fold and unfold.

When presented with a type, you can try to come up with values of that type by just applying introduction rules over and over. For example:

We want to find a term e such that:

. |- e : mu alpha. alpha -> alpha


. |- e : (mu alpha. alpha -> alpha) -> (mu alpha. alpha -> alpha)
. |- fold e : mu alpha. alpha -> alpha

Okay… what kind of e expression should e be if we want a value? Well, it’s got to have a -> type, so let’s make it a lambda:

x:(mu alpha. alpha -> alpha) |- e : (mu alpha. alpha -> alpha)
. |- \x:(mu alpha. alpha -> alpha). e : (mu alpha. alpha -> alpha) -> (mu alpha. alpha -> alpha)
. |- fold (\x. e) : mu alpha. alpha -> alpha

Okay… what should e be now? Well, we have x around, so that could work.

What else? What happens if you unfold x? Well, we have:

x:(mu alpha. alpha -> alpha) |- x : mu alpha. alpha -> alpha
x:(mu alpha. alpha -> alpha) |- unfold x : (mu alpha. alpha -> alpha) -> (mu alpha. alpha -> alpha)

What can we do with this? We could apply it… but to what? Well, something that has type mu alpha. alpha -> alpha. Like… well, x!

So another possible value is fold (\x. (unfold x) x), if we erase the subscripts.

What other values inhabit this type?

Problem 2: translating datatype definitions to recursive types automatically 20 points

In this problem, we will automatically translate datatype definitions to recursive algebraic types.

module Hw07 where

import qualified Data.Set as Set
import Data.Set (Set)

import qualified Data.Map as Map
import Data.Map (Map, (!))

We’ll represent definitions using the Defn data structure. A datatype definition for our purposes consists of a name and a list of constructors with their arguments.

data Defn = Defn { dName :: String,
                   dCtors :: [(String,[Type])] }

We’ll represent arguments using the following language of types.

data Type = 
  | TBool
  | TDatatype String
  | TArrow Type Type

By way of example, here’s a datatype definition for integer lists.

listDefn :: Defn
listDefn = Defn { dName = "IntList",
                  dCtors = [("Nil",[]),
                            ("Cons",[TInt,TDatatype "IntList"])] }

Here’s one for days of the week.

dayDefn :: Defn
dayDefn = Defn { dName = "Day",
                 dCtors = [("Sunday",[]),
                           ("Saturday",[])] }

Write a Defn for binary trees with integers stored at the nodes.

treeDefn :: Defn
treeDefn = undefined -- fill in here

We’re going to translate datatype definitions to a language of recursive algebraic types, as below.

data RecType = 
  | RTBool
  | RTVar String
  | RTUnit
  | RTPair RecType RecType
  | RTSum RecType RecType
  | RTMu String RecType
  | RTArrow RecType RecType
    deriving Show

First, write a function to determine whether such types are well formed—i.e., whether or not they contain unbound variables. Here’s the interesting rules for type well formedness:

alpha in G
G |- alpha : *

G,alpha |- t : *
G |- mu alpha. t : *

G |- t1 : *   G |- t2 : *
G |- t1 -> t2 : *
wellFormed :: RecType -> Bool
wellFormed = undefined

Now write a function that takes a Defn and produces a RecType. You’ll need to simply follow the recipe above. You can assume that the constructors for a given Defn only refer back to the current definition: that is, if TDatatype s occurs in the argument list for a constructor when defining a Defn named n, then you may assume that s == n.

For example, listDefn above is fine, but you don’t need to deal with a definition like:

treeListDefn :: Defn
treeListDefn = Defn { dName = "TreeList",
                      dCtors = [("LEmpty",[]),
                                ("LNode",[TDatatype "TreeList",
                                          TDatatype "IntList",
                                          TDatatype "TreeList"])] }

Here we violate the assumption, because TDatatype "IntList" appears in the LNode constructor.

simpleDefnToRec :: Defn -> RecType
simpleDefnToRec (Defn name ctors) = 

For extra credit: write a function that takes a Defn and produces a RecType, without any assumptions. You’ll need to change the interface from simpleDefnToRec. Feel free to come get help on this, but only after you’ve finished the rest of the assignment.

Problem 3: wearing the hair shirt: recursive datatypes in Haskell 20 points

newtype Mu f = Fold { unFold :: f (Mu f) }

Even if you don’t undersatnd this type, observe that Fold has the type f (Mu f) -> Mu f and unFold has the type Mu f -> f (Mu f). The type Mu f corresponds to μα. t, and the type f (Mu f) correspond to t[μα. t/α].

Here’s a definition for lists of arbitrary values:

data ListF a f = Nil | Cons a f
type List a = Mu (ListF a)

nil = Fold Nil
cons x xs = Fold $ Cons x xs

toList l = 
  case unFold l of
    Nil -> []
    Cons x xs -> x:toList xs

Write a similar definition for rose trees.

Write a preorder traversal of these rose trees. You’ll almost certainly want helper functions.

preorder :: RoseTree a -> List a
preorder t =