(* CS:3820, Fall 2018 *) (* Cesare Tinelli *) (* The University of Iowa *) (* F# examples seen in class, adapted from "Programming Language Concepts" by P. Sestoft, Springer 2012 *) (* Evaluation, checking, and compilation of object language expressions *) (* Stack machines for expression evaluation *) (* Object language expressions with variable bindings and nested scope *) (* Association lists map object language variables to their values *) let env = [("a", 3); ("c", 78); ("baf", 666); ("b", 111)] let emptyenv = [] (* the empty environment *) let rec lookup env x = match env with | [] -> failwith (x + " not found") | (k, v) :: env' -> if x = k then v else lookup env' x let cvalue = lookup env "c" (* ================================================================== *) (* Adding F#-style let binders to our expression language *) type expr = | CstI of int | Var of string | Prim of string * expr * expr | Let of string * expr * expr // new case (* Some expressions: *) // let z = 17 in z + z (in F#-like concrete syntax) // {val z = 17; z + z} (in Scala-like concrete syntax} let e1 = Let("z", CstI 17, Prim("+", Var "z", Var "z")) // let z = 17 in (let z = 22 in 100 * z) + z // {val z = 17; {val z = 22; 100 * z} + z} let e2 = Let("z", CstI 17, Prim("+", Let("z", CstI 22, Prim("*", CstI 100, Var "z")), Var "z")) // let z = 5 - 4 in 100 * z // {val z = 5 - 4; 100 * z} let e3 = Let("z", Prim("-", CstI 5, CstI 4), Prim("*", CstI 100, Var "z")) // (20 + (let x = 17 in z + 2)) + 30 // (20 + {val x = 17; z + 2}) + 30 let e4 = Prim("+", Prim("+", CstI 20, Let("z", CstI 17, Prim("+", Var "z", CstI 2))), CstI 30) // 2 * (let x = 3 in x + 4) // 2 * {val x = 3; x + 4} let e5 = Prim("*", CstI 2, Let("x", CstI 3, Prim("+", Var "x", CstI 4))) (* Evaluation of expressions with variables and bindings *) let rec eval e (env : (string * int) list) : int = match e with | CstI i -> i | Var x -> lookup env x | Prim("+", e1, e2) -> (eval e1 env) + (eval e2 env) | Prim("*", e1, e2) -> (eval e1 env) * (eval e2 env) | Prim("-", e1, e2) -> (eval e1 env) - (eval e2 env) | Prim _ -> failwith "unknown primitive" | Let(x, e1, e2) -> // i.e., let x = e1 in e2 // {val x = e1; e2} let v = eval e1 env in // evaluate e1 in input environment env let env' = (x, v) :: env in // extend env with new value for x eval e2 env' // evaluate e2 in new environment // Equivalent version of previous eval let rec eval e (env : (string * int) list) : int = match e with | CstI i -> i | Var x -> lookup env x | Prim("+", e1, e2) -> (eval e1 env) + (eval e2 env) | Prim("*", e1, e2) -> (eval e1 env) * (eval e2 env) | Prim("-", e1, e2) -> (eval e1 env) - (eval e2 env) | Prim _ -> failwith "unknown primitive" | Let(x, e1, e2) -> eval e2 ((x, eval e1 env) :: env) // more compact code // function run takes a *closed* expression (see below) // and evaluates it in the empty environment let run e = eval e [];; (* Execution trace for running (let z = 10 in z + 1) run (Let("z", CstI 10, Prim("+", Var "z", CstI 1))) ---> eval (Let("z", CstI 10, Prim("+", Var "z", CstI 1))) [] ---> eval (Prim("+", Var "z", CstI 1)) (("z", eval CstI 10 []) :: []) ---> eval (Prim("+", Var "z", CstI 1)) (("z", 10) :: []) ---> eval (Prim("+", Var "z", CstI 1)) [("z", 10)] ---> (eval (Var "z") [("z", 10)]) + (eval (CstI 1) [("z", 10)]) ---> (lookup [("z", 10)] "z") + (eval (CstI 1) [("z", 10)]) ---> 10 + (eval (CstI 1) [("z", 10)]) ---> 10 + 1 ---> 11 *) (* Execution trace for running (let y = 1 in let z = y in y + z) run (Let("y", CstI 1, Let("z", Var "y", Prim("+", Var "y", Var "z")))) ---> eval (Let("y", CstI 1, Let("z", Var "y", Prim("+", Var "y", Var "z")))) [] ---> eval (Let("z", Var "y", Prim("+", Var "y", Var "z"))) (("y", eval (CstI 1) []) :: env) ---> eval (Let("z", Var "y", Prim("+", Var "y", Var "z"))) (("y", 1) :: env) ---> eval (Let("z", Var "y", Prim("+", Var "y", Var "z"))) [("y", 1)] ---> eval (Prim("+", Var "y", Var "z")) (("z", eval (Var "y") [("y", 1)]) :: [("y", 1)]) ---> eval (Prim("+", Var "y", Var "z")) (("z", lookup [("y", 1)] "y") :: [("y", 1)]) ---> eval (Prim("+", Var "y", Var "z")) (("z", 1) :: [("y", 1)]) ---> eval (Prim("+", Var "y", Var "z")) [("z", 1); ("y", 1)] ---> (eval (Var "y") [("z", 1); ("y", 1)]) + (eval (Var "z") [("z", 1); ("y", 1)]) ---> (lookup [("z", 1); ("y", 1)]) "y") + (eval (Var "z") [("z", 1); ("y", 1)]) ---> 1 + (lookup [("z", 1); ("y", 1)] "z") ---> 1 + 1 ---> 2 *) (* ================================================================== *) (* Implementing finite sets in F# *) // Sets can be represented (not very efficiently) // as lists with no duplicated elements // set membership // (mem x s) is true iff x is in s let rec mem x s = match s with | [] -> false | a :: r when a = x -> true | a :: s -> mem x s // (union s1 s2) takes two lists representing sets and returns // a list representing their union // (i.e., returns a list without duplicates consisting of // all the elements of s1 and all the elements of s2) let rec union s1 s2 = match s1 with | [] -> s2 | x :: s1' when (mem x s2) -> union s1' s2 | x :: s1' -> union s1' (x :: s2) (* match s1 with | [] -> s2 | x :: s1' -> if mem x s2 then union s1' s2 // skip x because it is already in s2 else x :: (union s1' s2) // add x to the union of s1' and s2 *) // (diff s1 s2) takes two lists representing sets and returns // a list representing their difference // (i.e., returns a list without duplicates consisting of // all the elements of s1 that are not in s2) let rec diff s1 s2 = match s1 with | [] -> [] | x :: s1' -> if mem x s2 then diff s1' s2 else x :: (diff s1' s2) (* Exercise 1. Define a function intersect that takes two lists s1 and s2 representing sets and returns a list representing their intersection (i.e., returns a list with no duplicates consisting of all the elements of s1 that occur also in s2). 2. Define a function subset that takes two lists s1 and s2 representing sets and returns true iff the first set is contained in the second (i.e., returns true iff all the elements of s1 occur in s2). 3. Define a function setEq that takes two lists s1 and s2 representing sets and returns true iff the two sets contain exactly the same elements. *) (* ================================================================== *) (* Free variables in expressions Informally, a variable occurs *free* in an expression if it was not introduced by any enclosing let. A variable occurs *bound* in an expression if it never occurs free. Examples: let k = 49 in x * ( 3 + y) ^ free ^ free x + (let y = 5 in k -2 * y) ^ free ^ bound y + (let y = 5 in 2 * y) ^ free ^ bound x + (let y = z + 1 in 2 * y) ^ free ^ free ^ bound let x = 5 in x + (let y = z + 1 in 2 * y) - 3 + y ^ bound ^ free ^ bound ^ free *) (* Exercise Which variable occurrences are free and which are bound in the following expressions? 1. let z = x in z + x 2. let z = 3 in let y = z + 1 in x + y 3. let z = (let x = 4 in x + 5) in z * 2 4. let z = (let x = 4 in x + 5) + x in z * 2 5. let x = x + 1 in 2 * x *) // (freeVars e) returns a list consisting of all names // of variables that occur free in expression e let rec freeVars e : string list = match e with | CstI i -> [] | Var x -> [x] // a variable always occurs free in itself | Prim(_, e1, e2) -> union (freeVars e1) (freeVars e2) | Let(x, e1, e2) -> let v1 = freeVars e1 in let v2 = freeVars e2 in union v1 (diff v2 [x]) // let binds x in e2 but not in e1 freeVars e1 freeVars e2 freeVars e3 freeVars e4 (* Exercise Write the expression in the previous exercise as F# expressions of type aexpr. Then use freeVars to verify your answers to the previous exercise. *) // An expression is *closed* if it has no free variables let closed e = (freeVars e = [])