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 =
Nil
| Cons Int IntList
An IntList is either:
Nil, orCons n l, such thatnis anIntandlis anIntList.
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
so
. |- 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 =
TInt
| TBool
| TDatatype String
| TArrow Type TypeBy 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",[]),
("Monday",[]),
("Tuesday",[]),
("Wednesday",[]),
("Thursday",[]),
("Friday",[]),
("Saturday",[])] }Write a Defn for binary trees with integers stored at the nodes.
treeDefn :: Defn
treeDefn = undefined -- fill in hereWe’re going to translate datatype definitions to a language of recursive algebraic types, as below.
data RecType =
RTInt
| RTBool
| RTVar String
| RTUnit
| RTPair RecType RecType
| RTSum RecType RecType
| RTMu String RecType
| RTArrow RecType RecType
deriving ShowFirst, 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 = undefinedNow 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) =
undefinedFor 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 xsWrite 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 =
undefined