Homework must be submitted via D2L. All submitted files (2 for this assignment sol7A.hs and sol7B.e7) 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. For example.
-- Homework 7 Tom Smith tom_smith@gmail.com
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:
Language E7 is different from our other language in that it is composed of two kinds of files: Program files (i.e. xxx.e7) and Signature files (i.e. xxx.sig). Signature files contain information that describes a set of names and their associated attributes. Attributes include things like arities and types. E7's concretized abstract syntax is given by the following grammar:
sigFile := { signature } '(' defsig Id sigExp ')' progFile := { signature } module { globaldef | fundef | datadef | adtdef | import } 'main' exp signature := '(' 'signature' Id string ')' -- "Id" names a set of items, "string" tells which file. module := '(' 'module' Id in sigExp out sigExp ')' 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 like 'a' | string -- familiar "abc" syntax for list of Char | '(' = exp exp ') -- Character equality (ill-typed on other things) | '(' '@' exp { exp } ')' -- function application | '(' 'lambda' ( {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 } ')' import := '(' 'import' string implements sigExp ')' | '(' 'import' string hiding '(' {Id | id } ')' ')' sigExp := Id | 'prelude' | 'everything' | '(' 'sig' { sigItem } ')' | '(' hide' sigExp { Id | id } ')' | '(' 'union' { sigExp } ')' 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 sigExpin the read-type-eval-print loop. For example to some set of definitions are available in simple.e7, we load the file and then use the ':s' feature. See the transcript below.
enter Exp> :s prelude *** Types (Bool ), (Int ), (Char ), (List a), (Pair a b) *** Vars *** Constructors #nil::[a] #cons::(a-> [a]-> [a]) #pair::(a-> b-> (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) *** Types (Char ), (List a), (Pair a b) *** Vars *** Constructors #cons::(a-> [a]-> [a]) #pair::(a-> b-> (a . b)) enter Exp>
We may create our own unique set of definitions by using an explicit signature.
:s (sig (val x Int) (data (T) (#a Int) (#b))) *** Types (T) *** Vars x::Int *** Constructors #a::(Int-> T) #b::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)))) *** Types (T ), (Bool ), (Int ), (Char ), (List a), (Pair a b) *** Vars x::Int *** Constructors #a::(Int-> T) #b::T #nil::[a] #cons::(a-> [a]-> [a]) #pair::(a-> b-> (a . b))
Finally, we may compute the set of everything defined in the file by using the everything sig expression.
:s everything *** Types (Bool ), (Int ), (Char ), (List a), (Pair a b) *** Vars odd::(Int-> Bool) even::(Int-> Bool) eq::(Int-> Int-> Bool) and::(Bool-> Bool-> Bool) ten::Int *** Constructors #nil::[a] #cons::(a-> [a]-> [a]) #pair::(a-> b-> (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 describe exactly the set of items exported from a file by using an 'out' clause. The system checks that there is a defintion for every item in the 'out' set. Definitions not in the 'out' set are "hidden" and are not exported.
(signature A "A.sig") (signature B "B.sig") (defsig C (union A B (sig (val c Int) (val d Bool))))
We use signature declarations (like (signature A "A.sig")) at the top of both signature files and program files. The variables they bind (like A) live in a separate name space from program variables. These variables always start with a upper case letter, and can be used in sigExp's. A more usefule signature file for table lookup is given below.
(defsig EnvSig (sig (type (Env a)) (data (Result a) (#found a) (#notFound)) (val lookup ((Env a) -> Int -> (Result a))) (val extend (Int -> a -> (Env a) -> (Env a))) (val empty (Env a))))
To see how the user defines types, study the file types.e7
(signature EnvSig "env.sig") (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 env.sig.
What to do
Your file should have the following module declaration.
(module Env2 in prelude out (file "env.sig"))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 env.sig.
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.)