Homework must be submitted via D2L. All submitted files (n for this assignment sol7A.hs) must be submitted in the appropriate D2L directory in the drop box HW7. It is your responsibility to submit the homework in the proper format with the proper names.
All programs mentioned can be downloaded from the this document
In this homework we consider a variant of simple language with user defined types and a simple module system we call E7. The language, E7, has the following features:
E7's concretized abstract syntax is given by the following grammar:
prog := module { globaldef | fundef | datadef | adtdef | sigdef | import } 'main' exp typ := 'Int' | 'Bool' | 'Char' | '(' Id {typ} ')' -- User defined types | '(' typ '.' typ ')' -- syntax for (Pair x y) | '[' typ ']' -- syntax for (List x) | letter -- polymorphic type variable exp := id -- imperative features | '(' ':=' id exp ')' | '(' 'local' '(' { id exp } ')' exp ')' | '(' ':=' exp exp ')' | '(' 'write' exp ')' | '(' 'block' { exp } ')' | '(' 'while' exp exp ')' | True -- Booleans | False | '(' 'if' exp exp exp ')' | int -- Integers | '(' '+' exp exp ')' | '(' '-' exp exp ')' | '(' '*' exp exp ')' | '(' '/' exp exp ')' | '(' '<=' exp exp ')' | char -- Characters | string -- familiar "abc" syntax for list of Char | '(' = exp exp ') | '(' '@' exp { exp } ')' -- function application | '(' '\' ( {id} ) exp ')' -- lambda abstraction (1st class anonymous functions) | '(' '#' id {exp} ')' -- data construction (like: cons,nil,pair) | '(' '!' id exp ')' -- data selection (like: head,tail,fst) | '(' '?' id int exp ')' -- data predicates (like: null) globaldef := '(' 'global' vname typ exp ')' fundef := '(' 'fun' id typ '(' { id typ } ')' exp ')' datadef := '(' data '('Id {id} ')' { '(' '#' id {typ} ')' } ')' adtdef := '(' 'adt' '('Id {id} ')' typ { globaldef | fundef | datadef } ')' sigdef := '(' 'signature' Id {sigitem} ')' | '(' 'signature' string ')' -- (signature "test.e7") read signature from a file import := '(' 'import' string implements sigExp ')' | '(' 'import' string hiding '(' {Id | id } ')' ')' module := '(' 'module' Id in sigExp out sigExp ')' sigExp := Id | 'prelude' | 'everything' | '(' 'sig' { sigItem } ')' | '(' hide' sigExp '(' {Id | id } ')' ')' | '(' 'union' { sigExp } ')' | '(' 'file' string ')' sigItem := '(' val id typ ') '(' data '('Id {id} ')' { '(' '#' id {typ} ')' } ')' '(' type '(' Id {typ} ')' ')' id := lower { lower | upper | digit } Id := upper { lower | upper | digit }
Note that the syntax for expressions has been divided into groups, where each group supports one aspect of the language. The types for lists and pairs are no longer primitive. These types come predefined.
(data (List a) (#nil) (#cons a (List a))) (data (Pair a b) (#pair a b))
A sample file, which implements the operations on lists and pairs (nil,cons,head,tail,null,pair,fst, and snd) and many other familiar functions and types is typedlists.e7.
(module Simple in prelude out everything) (global ten Int 10) (fun and Bool (x Bool y Bool) (if x y x)) (fun eq Bool (x Int y Int) (@and (<= x y) (<= y x))) { functions can be mutually recursive } (fun even Bool (n Int) (if (@eq n 0) True (@ odd (- n 1)))) (fun odd Bool (n Int) (if (@eq n 0) False (@ even (- n 1)))) main (@odd 3)
A module statement like (module Simple in prelude out everything) has a name, and two signature expressions (prelude and everything). The 'in' expression tells programmer what things are defined in some other program file, and the 'out' expression tells what is defined by this file.
Signature expressions are formed by the syntax shown above. And every expression 'evaluates' to a set of definitions. In language E7 one may view this set by typing ':s sigExp' in the read-type-eval-print loop. For example see what definitions are in the prelude, see the transcript below.
enter Exp> :s prelude #nil::[a], #cons::(a-> [a]-> [a]), #pair::(a-> b-> (a . b)) (Bool ), (Int ), (Char ), (List a), (Pair a b)
Note that the file simple.e7 gets only these definition from the context (other files), since it has the in prelude clause in its module definition. We may subtract definitions from a set by using the hide expression.
enter Exp> :s (hide prelude (Int Bool nil)) #cons::(a-> [a]-> [a]), #pair::(a-> b-> (a . b)) (Char ), (List a), (Pair a b)
We may create our own unique set of definitions by using an explicit signature.
enter Exp> :s (sig (val x Int) (data (T) (#a Int) (#b))) x::Int #a::(Int-> T), #b::T (T )
Or we may union together multiple sets by using the union operator.
enter Exp> :s (union prelude (sig (val x Int) (data (T) (#a Int) (#b)))) x::Int #a::(Int-> T), #b::T, #nil::[a], #cons::(a-> [a]-> [a]), #pair::(a-> b-> (a . b)) (T ), (Bool ), (Int ), (Char ), (List a), (Pair a b)
Finally, we may compute the set of everything defined in the file by using the everything sig expression.
enter Exp> :s everything odd::(Int-> Bool), even::(Int-> Bool), eq::(Int-> Int-> Bool), and::(Bool-> Bool-> Bool), ten::Int #nil::[a], #cons::(a-> [a]-> [a]), #pair::(a-> b-> (a . b)) (Bool ), (Int ), (Char ), (List a), (Pair a b)
The 'in' and 'out' clauses compute a set of definitions for each file. The system checks that any imported file is only imported into a context where its 'in' set is available. The user may restrict what is exported from a file by using an 'out' clause that removes some definitions.
To see how the user defines types, study the file types.e7
(module Types in prelude out everything) (data (Tree a) (#tip a) (#fork (Tree a) (Tree a))) (data (Color) (#red) (#blue) (#green)) (data (Result a) (#found a) (#notFound)) (global nil [a] (# nil)) (fun head h (x [h]) (!cons 0 x)) (fun tail [a] (x [a]) (!cons 1 x)) (fun fst a (x (a.b)) (!pair 0 x)) (fun snd b (x (a.b)) (!pair 1 x)) (fun null Bool (x [a]) (?nil x)) (fun consP Bool (x [a]) (?cons x)) { Basic boolean support } (fun and Bool (x Bool y Bool) (if x y x)) { Equality on integers } (fun eq Bool (x Int y Int) (@and (<= x y) (<= y x))) (adt (Env a) [(Int . a)] (global empty (Env a) nil) (fun extend (Env a) (key Int object a table (Env a)) (#cons (#pair key object) table)) (fun lookup (Result a) (tab (Env a) key Int) (if (?nil tab) (#notFound) (if (@eq key (@fst (@head tab))) (#found (@snd (@head tab))) (@lookup (@tail tab) key)))) ) main 0
We see that there are two ways to define a new type. A data definition (similar to Haskell) and an adt definition (an Abstract Data Type). A data definition introduces a new type with multiple constructors (tip, fork, red, blue, green, Found, and notfound). Such a type can be parameterized (like Tree and Result) or just be a type (like Color). Note that type names must start with a capital letter. There are three kinds of things one might want to do to a data type.
An adt defines a type in terms of what operations it has. For example the type (Env a) has only the operations, empty, extend, and lookup. An adt has an implementation type. The implementation type for (Env a) is the type [(Char . a)]. Inside the adt the defined type (Env a) and the implementation type [(Char . a)] are the same. Outside the adt, the operations work only on values the abstract type. For example even though empty is defined to be nil. It can't be used that way.
enter Exp> (#cons 4 empty) user error ( *** Error, near "keyboard input (#cons 4 empty)" (line 1, column 10) [t92] =/= (Env t93) (Different types) Checking construction arg empty While inferring the type of empty Expected type: [t92] Computed type: (Env t93))
Consider an the E7 abstract data type (ADT) for environments, The ADT is defined in the file types.e7
(adt (Env a) [(Int . a)] (global empty (Env a) nil) (fun extend (Env a) (key Int object a table (Env a)) (#cons (#pair key object) table)) (fun lookup (Result a) (tab (Env a) key Int) (if (?nil tab) (#notFound) (if (@eq key (@fst (@head tab))) (#found (@snd (@head tab))) (@lookup (@tail tab) key)))) )
Its signature is defined in the file envSig.e7.
What to do
Your file should start with the prelude
(module Env2 in prelude out (file "envSig.e7"))This means you cannot depend upon anything, except what is in the prelude, and you should export, only those things in the signature in file envSig.e7.
Use the following definition of trees
(data (Tree a) (#leaf) (#node Int a (Tree a) (Tree a)))
Do not alter the existing Env interface and don't accidentally alter the behavior of the operators. In particular, remember that if an environment is extended twice with the same identifier, the more recent extension "hides" the previous one.
Put your new module definition (only) into a file sol7A.e7 and submit it. (Note: you will almost certainly want to test your implementation, but don't include the testing code in your submission.)