2010-01-29 - Implementing `with' Evaluation (contd.) - Formal Specs - Lazy vs Eager Evaluation - de Bruijn Indices - Functions & First Class Function Values - Implementing First Class Function Values - The FLANG Language ======================================================================== Reminder: * We started doing substitution, with a `let'-like form: `with'. * Reasons for using bindings: - Avoid writing expressions twice. -> More expressive language (can express identity). -> Duplicating is bad! (=> DRY, Don't Repeat Yourself) --> Static redundancy. - Avoid redundant computations. --> Dynamic redundancy. * BNF: ::= | { + } | { - } | { * } | { / } | { with { } } | Note that we had to introduce two new rules: one for introducing an identifier, and one for using it. * Type definition: (define-type WAE [Num (n Number)] [Add (lhs WAE) (rhs WAE)] [Sub (lhs WAE) (rhs WAE)] [Mul (lhs WAE) (rhs WAE)] [Div (lhs WAE) (rhs WAE)] [Id (name Symbol)] [With (name Symbol) (named WAE) (body WAE)]) * Parser: (: parse-sexpr : Sexpr -> WAE) ;; to convert s-expressions into WAEs (define (parse-sexpr sexpr) (match sexpr [(number: n) (Num n)] [(symbol: name) (Id name)] [(cons 'with more) (match sexpr [(list 'with (list (symbol: name) named) body) (With name (parse-sexpr named) (parse-sexpr body))] [else (error 'parse-sexpr "bad `with' syntax in ~s" sexpr)])] [(list '+ lhs rhs) (Add (parse-sexpr lhs) (parse-sexpr rhs))] [(list '- lhs rhs) (Sub (parse-sexpr lhs) (parse-sexpr rhs))] [(list '* lhs rhs) (Mul (parse-sexpr lhs) (parse-sexpr rhs))] [(list '/ lhs rhs) (Mul (parse-sexpr lhs) (parse-sexpr rhs))] [else (error 'parse-sexpr "bad syntax in ~s" sexpr)])) * We need to define substitution. Terms: 1. Binding Instance. 2. Scope. 3. Bound Instance. 4. Free Instance. * After lots of attempts: e[v/i] -- To substitute an identifier `i' in an expression `e' with an expression `v', replace all instances of `i' that are free in `e' with the expression `v'. * Implemented the code, and again, needed to fix a few bugs: (: subst : WAE Symbol WAE -> WAE) ;; substitutes the second argument with the third argument in the ;; first argument, as per the rules of substitution; the resulting ;; expression contains no free instances of the second argument (define (subst expr from to) (cases expr [(Num n) expr] [(Add l r) (Add (subst l from to) (subst r from to))] [(Sub l r) (Sub (subst l from to) (subst r from to))] [(Mul l r) (Mul (subst l from to) (subst r from to))] [(Div l r) (Div (subst l from to) (subst r from to))] [(Id name) (if (eq? name from) to expr)] [(With bound-id named-expr bound-body) (With bound-id (subst named-expr from to) (if (eq? bound-id from) bound-body (subst bound-body from to)))])) (Note that the bugs that we fixed clarify the exact way that our scopes work: in `{with {x 2} {with {x {+ x 2}} x}}', the scope of the first `x' is: ^^^^^^^) * We then extended the AE evaluation rules: eval(...) = ... same as the AE rules ... eval({with {x E1} E2}) = eval(E2[eval(E1)/x]) eval(id) = error! and noted the possible type problem. * The above translated into a Scheme definition for an `eval' function (with a hack to avoid the type issue): (: eval : WAE -> Number) ;; evaluates WAE expressions by reducing them to numbers (define (eval expr) (cases expr [(Num n) n] [(Add l r) (+ (eval l) (eval r))] [(Sub l r) (- (eval l) (eval r))] [(Mul l r) (* (eval l) (eval r))] [(Div l r) (/ (eval l) (eval r))] [(With bound-id named-expr bound-body) (eval (subst bound-body bound-id (Num (eval named-expr))))] [(Id name) (error 'eval "free identifier: ~s" name)])) ======================================================================== >>> Formal Specs Note the formal definitions that were included in the WAE code. They are ways of describing pieces of our language that are more formal than plain English, but still not as formal (and as verbose) as the actual code. A formal definition of `subst': (`N' is a , `E1', `E2' are s, `x' is some , `y' is a *different* ) N[v/x] = N {+ E1 E2}[v/x] = {+ E1[v/x] E2[v/x]} {- E1 E2}[v/x] = {- E1[v/x] E2[v/x]} {* E1 E2}[v/x] = {* E1[v/x] E2[v/x]} {/ E1 E2}[v/x] = {* E1[v/x] E2[v/x]} y[v/x] = y x[v/x] = v {with {y E1} E2}[v/x] = {with {y E1[v/x]} E2[v/x]} {with {x E1} E2}[v/x] = {with {x E1[v/x]} E2} And a formal definition of `eval': eval(N) = N eval({+ E1 E2}) = eval(E1) + eval(E2) eval({- E1 E2}) = eval(E1) - eval(E2) eval({* E1 E2}) = eval(E1) * eval(E2) eval({/ E1 E2}) = eval(E1) * eval(E2) eval(id) = error! eval({with {x E1} E2}) = eval(E2[eval(E1)/x]) ======================================================================== >>> Lazy vs Eager Evaluation As we have previously seen, there are two basic approaches for evaluation: either eager or lazy. In lazy evaluation, bindings are used for sort of textual references -- it is only for avoiding writing an expression twice, but the associated computation is done twice anyway. In eager evaluation, we eliminate not only the textual redundancy, but also the computation. Which evaluation method did our evaluator use? The relevant piece of formalism is the treatment of `with': eval({with {x E1} E2}) = eval(E2[eval(E1)/x]) And the matching piece of code is: [(With bound-id named-expr bound-body) (eval (subst bound-body bound-id (Num (eval named-expr))))] How do we make this lazy? In the formal equation: eval({with {x E1} E2}) = eval(E2[E1/x]) and in the code: (: eval : WAE -> Number) ;; evaluates WAE expressions by reducing them to numbers (define (eval expr) (cases expr [(Num n) n] [(Add l r) (+ (eval l) (eval r))] [(Sub l r) (- (eval l) (eval r))] [(Mul l r) (* (eval l) (eval r))] [(With bound-id named-expr bound-body) (eval (subst bound-body bound-id named-expr))] ; <- no eval and no Num wrapping [(Id name) (error 'eval "free identifier: ~s" name)])) We can verify the way this works by tracing `eval' (compare the trace you get for the two versions): > (trace eval) > (run "{with {x {+ 1 2}} {* x x}}") Ignoring the traces for now, the modified WAE interpreter works as before, specifically, all tests pass. So the question is whether the language we get is actually different than the one we had before. One difference is in execution speed, but we can't really notice a difference, and we care more about meaning. Is there any program that will run differently in the two languages? The main feature of the lazy evaluator is that it is not evaluating the named expression until it is actually needed. As we have seen, this leads to duplicating computations if the bound identifier is used more than once -- meaning that it does not eliminate the dynamic redundancy. But what if the bound identifier is not used at all? In that case the named expression simply evaporates. This is a good hint at an expression that behaves differently in the two languages -- if we add division to both languages, we get a different result when we try running: {with {x {/ 8 0}} 7} The eager evaluator stops with an error when it tries evaluating the division -- and the lazy evaluator simply ignores it. Even without division, we get a similar behavior for {with {x y} 7} but it is questionable whether the fact that this evaluates to 7 is correct behavior -- we really want to forbid program that use free variable. Furthermore, there is an issue with name capturing -- we don't want to substitute an expression into a context that captures some of its free variables. But our substitution allows just that, which is usually not a problem because by the time we do the substitution, the named expression should not have free variables that need to be replaced. However, consider evaluating this program: {with {y x} {with {x 2} {+ x y}}} under the two evaluation regimens: the eager version stops with an error, and the lazy version succeed. This points at a bug in our substitution, or rather not dealing with an issue that we do not encounter. So the summary is: as long as the initial program is correct, both evaluation regimens produce the same results. If a program contains free variables, they might get captured in a naive lazy evaluator implementation (but this is a bug that should be fixed). Also, there are some cases where eager evaluation runs into a run-time problem which does not happen in a lazy evaluator because the expression is not used. It is possible to prove that when you evaluate an expression, if there is an error that can be avoided, lazy evaluation will always avoid it, whereas an eager evaluator will always run into it. On the other hand, lazy evaluators are usually slower than eager evaluator, so it's a speed vs. robustness trade-off. Note that with lazy evaluation we say that an identifier is bound to an expression rather than a value. (Again, this is why the eager version needed to wrap `eval's result in a `Num' and this one doesn't.) (It is possible to change things and get a more well behaved substitution, we basically will need to find if a capture might happen, and rename things to avoid it. For example, {with {y E1} E2}[v/x] if `x' and `y' are equal = {with {y E1[v/x]} E2} = {with {x E1[v/x]} E2} if `y' has a free occurrence in `v' = {with {y1 E1[v/x]} E2[y1/y][v/x]} otherwise = {with {x E1[v/x]} E2[v/x]} But you can see that this is much more complicated (more code: requires a `free-in' predicate, being able to invent new "fresh" names, etc). And it's not even the end of that story...) ======================================================================== >>> de Bruijn Indices This whole story revolves around names, specifically, name capture is a problem that should always be avoided (it is one major source of PL headaches). But are names the only way we can use bindings? There is a least one alternative way: note that the only thing we used names for are for references. We don't really care what the name is, which is pretty obvious when we consider the two WAE expressions: {with {x 5} {+ x x}} {with {y 5} {+ y y}} and the two Scheme function definitions: (define (foo x) (list x x)) (define (foo y) (list y y)) Both of these show a pair of expressions that we should consider as equal in some sense (this is called "alpha-equality"). The only thing we care about is what variable points where: the binding structure is the only thing that matters. In other words, as long as DrScheme produces the same arrows when we use Check Syntax, we consider the program to be the same, regardless of name choices (for argument names and local names, not for global names like `foo' in the above). The alternative idea uses this principle: if all we care about is where the arrows go, then simply get rid of the names... Instead of referencing a binding through its name, just specify which of the surrounding scopes we want to refer to. For example, instead of: {with {x 5} {with {y 6} {+ x y}}} we can use a new `reference' syntax -- "[N]" -- and use this instead of the above: {with 5 {with 6 {+ [1] [0]}}} So the rules for [N] are -- [0] is the value bound in the current scope, [1] is the value from the next one up etc. Of course, to do this translation, we have to know the precise scope rules. Two more complicated examples: {with {x 5} {+ x {with {y 6} {+ x y}}}} is translated to: {with 5 {+ [0] {with 6 {+ [1] [0]}}}} (note how `x' appears as a different reference based on where it appeared in the original code.) Even more subtle: {with {x 5} {with {y {+ x 1}} {+ x y}}} is translated to: {with 5 {with {+ [0] 1} {+ [1] [0]}}} because the inner `with' does not have its own named expression in its scope, so the named expression is immediately in the scope of the outer `with'. This is called "de Bruijn Indices": instead of referencing identifiers by their name, we use an index into the surrounding binding context. The major disadvantage, as can be seen in the above examples, is that it is not convenient for humans to work with. Specifically, the same identifier is referenced using different numbers, which makes it hard to understand what some code is doing. However, practically all compilers use this for compiled code (think about stack pointers). For example, GCC compiles this code: { int x = 5; { int y = x + 1; return x + y; } } to: subl $8, %esp movl $5, -4(%ebp) movl -4(%ebp), %eax incl %eax movl %eax, -8(%ebp) movl -8(%ebp), %eax addl -4(%ebp), %eax ======================================================================== >>> Functions & First Class Function Values Now that we have a form for local bindings, which forced us to deal with proper substitutions and everything that is related, we can get to functions. The concept of a function is itself very close to substitution, and to our `with' form. For example, when we write: {with {x 5} {* x x}} then the "{* x x}" body is itself parametrized over some value for `x'. If we take this expression and take out the "5", we're left with something that looks like a function: {with {x} {* x x}} We only need to replace `with' to indicate the difference from a real `with' -- this form is missing a value: {fun {x} {* x x}} Now we have a new form in our language, one that should have a function as its meaning. As done with the `with' expression, we also need a form to use these functions. We can use `call' for this, so we want: {call {fun {x} {* x x}} 5} to be the same as the original thing we started with -- the `fun' expression is like the `with' expression with no value, and applying it on `5' is the same getting back to: {with {x 5} {* x x}} Of course, this does not help much -- all we get is a way to use local bindings that is more verbose from what we started with. What we're really missing is a way to name these functions. If we get the right evaluation rules, we can evaluate a `fun' expression to some value -- which will allow us to bind it to a variable using `with'. Something like this: {with {sqr {fun {x} {* x x}}} {+ {call sqr 5} {call sqr 6}}} In this expression, we say that `x' is the formal parameter (or argument), and the `5' and `6' are actual parameters (sometimes abbreviated as formals and actuals). [A different approach was used in the homework: a function definition is much like a `with' form but without any value, and dealing with a function application is much like dealing with a `with' form, except that the value is from one place (the call site), and the body expression and named variable are from a different place (the function definition).] ======================================================================== >>> Implementing First Class Function Values This is a simple plan, but it is directly related to how functions are used in our language -- there are three basic approaches that classify programming languages: 1. First order: functions are not real values. They cannot be used or returned as values by other functions. This means that they cannot be stored in data values. This is what you are/will be implementing in homework 3, and what most `conventional' languages have. 2. Higher order: functions can receive and return other functions as values. This is what you get in C. 3. First class: functions are values with all the rights of other values. In particular, they can be supplied to other functions, returned from functions, stored in data structures, and new functions can be created at run-time. The last category is the most interesting one. Back in the old days, complex expressions were not first-class in that they could not be freely composed. This is still the case in machine-code: as we've seen earlier, to compute an expression such as (-b + sqrt(b^2 - 4*a*c)) / 2a you have to do something like this: x = b * b y = 4 * a y = y * c x = x - y x = sqrt(x) y = -b x = y + x y = 2 * a s = x / y In other words, every intermediate value needs to have its own name. But with proper ("high-level") programming languages (at least most of them...) you can just write the original expression, with no names for these values. With first-class functions something similar happens -- it is possible to have complex expressions that consume and return functions, and they do not need to be named. What we get with our `fun' expression (if we can make it work) is exactly this: it generates a function, and you can choose to either bind it to a name, or not. This has a major effect on the "personality" of a programming language as we will see. In fact, just adding this feature will make our language much more advanced than some popular languages you know so far. ======================================================================== Quick Example: the following is working JavaScript code, that uses first class functions. function foo(x) { function bar(y) { return x + y; } return bar; } function main() { var f = foo(1); var g = foo(10); alert(">> "+ f(2) + ", " + g(2)); } GCC includes extensions that allow internal function definitions, but it still does not have first class functions -- trying to do the above is broken: #include typedef int(*int2int)(int); int2int foo(int x) { int bar(int y) { return x + y; } return bar; } int main() { int2int f = foo(1); int2int g = foo(10); printf(">> %d, %d\n", f(2), g(2)); } ======================================================================== >>> The FLANG Language Now for the implementation -- we call this new language FLANG. First, the BNF: ::= | { + } | { - } | { * } | { / } | { with { } } | | { fun { } } | { call } And the matching type definition: (define-type FLANG [Num (n Number)] [Add (lhs FLANG) (rhs FLANG)] [Sub (lhs FLANG) (rhs FLANG)] [Mul (lhs FLANG) (rhs FLANG)] [Div (lhs FLANG) (rhs FLANG)] [Id (name Symbol)] [With (name Symbol) (named FLANG) (body FLANG)] [Fun (name Symbol) (body FLANG)] [Call (fun-expr FLANG) (arg-expr FLANG)]) The parser for this grammar is, as usual, straightforward: (: parse-sexpr : Sexpr -> FLANG) ;; to convert s-expressions into FLANGs (define (parse-sexpr sexpr) (match sexpr [(number: n) (Num n)] [(symbol: name) (Id name)] [(cons 'with more) (match sexpr [(list 'with (list (symbol: name) named) body) (With name (parse-sexpr named) (parse-sexpr body))] [else (error 'parse-sexpr "bad `with' syntax in ~s" sexpr)])] [(cons 'fun more) (match sexpr [(list 'fun (list (symbol: name)) body) (Fun name (parse-sexpr body))] [else (error 'parse-sexpr "bad `fun' syntax in ~s" sexpr)])] [(list '+ lhs rhs) (Add (parse-sexpr lhs) (parse-sexpr rhs))] [(list '- lhs rhs) (Sub (parse-sexpr lhs) (parse-sexpr rhs))] [(list '* lhs rhs) (Mul (parse-sexpr lhs) (parse-sexpr rhs))] [(list '/ lhs rhs) (Div (parse-sexpr lhs) (parse-sexpr rhs))] [(list 'call fun arg) (Call (parse-sexpr fun) (parse-sexpr arg))] [else (error 'parse-sexpr "bad syntax in ~s" sexpr)])) We also need to patch up the substitution function to deal with these things. The scoping rule for the new function form is, unsurprisingly, similar to the rule of `with', except that there is no extra expression now, and the scoping rule for `call' is the same as for the arithmetic operators: N[v/x] = N {+ E1 E2}[v/x] = {+ E1[v/x] E2[v/x]} {- E1 E2}[v/x] = {- E1[v/x] E2[v/x]} {* E1 E2}[v/x] = {* E1[v/x] E2[v/x]} {/ E1 E2}[v/x] = {/ E1[v/x] E2[v/x]} y[v/x] = y x[v/x] = v {with {y E1} E2}[v/x] = {with {y E1[v/x]} E2[v/x]} {with {x E1} E2}[v/x] = {with {x E1[v/x]} E2} {call E1 E2}[v/x] = {call E1[v/x] E2[v/x]} {fun {y} E}[v/x] = {fun {y} E[v/x]} {fun {x} E}[v/x] = {fun {x} E} And the matching code: (: subst : FLANG Symbol FLANG -> FLANG) ;; substitutes the second argument with the third argument in the ;; first argument, as per the rules of substitution; the resulting ;; expression contains no free instances of the second argument (define (subst expr from to) (cases expr [(Num n) expr] [(Add l r) (Add (subst l from to) (subst r from to))] [(Sub l r) (Sub (subst l from to) (subst r from to))] [(Mul l r) (Mul (subst l from to) (subst r from to))] [(Div l r) (Div (subst l from to) (subst r from to))] [(Id name) (if (eq? name from) to expr)] [(With bound-id named-expr bound-body) (With bound-id (subst named-expr from to) (if (eq? bound-id from) bound-body (subst bound-body from to)))] [(Call l r) (Call (subst l from to) (subst r from to))] [(Fun bound-id bound-body) (if (eq? bound-id from) expr (Fun bound-id (subst bound-body from to)))])) ======================================================================== Now, before we start working on an evaluator, we need to decide on what exactly do we use to represent values of this language. Before we had functions, we had only numbers and we used (Scheme) numbers to represent them. Now we have two kinds of values -- numbers and functions. It seems easy enough to continue using Scheme numbers to represent numbers, but what about functions? What should be the result of evaluating {fun {x} {+ x 1}} ? Well, this *is* the new toy we have: it is a function *value*, which is something that can be used just like numbers, but instead of arithmetic operations, we can `call' these things. To accommodate this, we will change our implementation strategy a little: we will use our syntax objects for numbers (`(Num n)' instead of just `n'), which will be a little inconvenient when we do the arithmetic operations, but it will simplify life by making it possible to evaluate functions in a similar way: simply return their own syntax object as their values. This means that evaluating: (Add (Num 1) (Num 2)) now yields (Num 3) and a number `(Num 5)' evaluates to `(Num 5)'. In a similar way, `(Fun 'x (Num 2))' evaluates to `(Fun 'x (Num 2))'. Why would this work? Well, because `call' will be very similar to `with' -- the only difference is that its arguments are ordered a little differently, being retrieved from the function that is applied and the argument. The formal evaluation rules are therefore treating functions like numbers, and use the syntax object to represent both values: eval(N) = N eval({+ E1 E2}) = eval(E1) + eval(E2) eval({- E1 E2}) = eval(E1) - eval(E2) eval({* E1 E2}) = eval(E1) * eval(E2) eval({/ E1 E2}) = eval(E1) / eval(E2) eval(id) = error! eval({with {x E1} E2}) = eval(E2[eval(E1)/x]) eval(FUN) = FUN ; assuming FUN is a function expression eval({call E1 E2}) = eval(Ef[eval(E2)/x]) if eval(E1)={fun {x} Ef} = error! otherwise Note that the last rule could be written using a translation to a `with' expression: eval({call E1 E2}) = eval({with {x E2} Ef}) if eval(E1)={fun {x} Ef} = error! otherwise And alternatively, we could specify `with' using `call' and `fun': eval({with {x E1} E2}) = eval({call {fun {x} E2} E1}) There is a small problem in these rules: we now have two kinds of values, so we need to check the arithmetic operation's arguments too: eval({+ E1 E2}) = eval(E1) + eval(E2) if eval(E1) & eval(E2) are numbers otherwise error! ... The corresponding code is: (: eval : FLANG -> FLANG) ; <- note return type ;; evaluates FLANG expressions by reducing them to *expressions* (define (eval expr) (cases expr [(Num n) expr] ; <- change here [(Add l r) (arith-op + (eval l) (eval r))] [(Sub l r) (arith-op - (eval l) (eval r))] [(Mul l r) (arith-op * (eval l) (eval r))] [(Div l r) (arith-op / (eval l) (eval r))] [(With bound-id named-expr bound-body) (eval (subst bound-body bound-id (eval named-expr)))] ; <- no `(Num ...)' [(Id name) (error 'eval "free identifier: ~s" name)] [(Fun bound-id bound-body) expr] ; <- similar to `Num' [(Call (Fun bound-id bound-body) arg-expr) ; <- nested pattern (eval (subst bound-body ; <- just like `with' bound-id (eval arg-expr)))] [(Call something arg-expr) (error 'eval "`call' expects a function, got: ~s" something)])) Where the `arith-op' function is in charge of checking that the input values are numbers (represented as FLANG numbers), translating them to plain numbers, performing the Scheme operation, then re-wrapping the result in a `Num'. Note how its type indicates that it is a higher-order function. (: arith-op : (Number Number -> Number) FLANG FLANG -> FLANG) ;; gets a Scheme numeric binary operator, and uses it within a FLANG ;; `Num' wrapper (note H.O type) (define (arith-op op expr1 expr2) (: Num->number : FLANG -> Number) (define (Num->number e) (cases e [(Num n) n] [else (error 'arith-op "expects a number, got: ~s" e)])) (Num (op (Num->number expr1) (Num->number expr2)))) We can also make things a little easier to use if we make `run' convert the result to a number: (: run : String -> Number) ;; evaluate a FLANG program contained in a string (define (run str) (let ([result (eval (parse str))]) (cases result [(Num n) n] [else (error 'run "evaluation returned a non-number: ~s" result)]))) Adding few simple tests we get: ---------------------------------------------------------------------- ;; The Flang interpreter #lang pl #| The grammar: ::= | { + } | { - } | { * } | { / } | { with { } } | | { fun { } } | { call } Evaluation rules: subst: N[v/x] = N {+ E1 E2}[v/x] = {+ E1[v/x] E2[v/x]} {- E1 E2}[v/x] = {- E1[v/x] E2[v/x]} {* E1 E2}[v/x] = {* E1[v/x] E2[v/x]} {/ E1 E2}[v/x] = {/ E1[v/x] E2[v/x]} y[v/x] = y x[v/x] = v {with {y E1} E2}[v/x] = {with {y E1[v/x]} E2[v/x]} ; if y =/= x {with {x E1} E2}[v/x] = {with {x E1[v/x]} E2} {call E1 E2}[v/x] = {call E1[v/x] E2[v/x]} {fun {y} E}[v/x] = {fun {y} E[v/x]} ; if y =/= x {fun {x} E}[v/x] = {fun {x} E} eval: eval(N) = N eval({+ E1 E2}) = eval(E1) + eval(E2) \ if both E1 and E2 eval({- E1 E2}) = eval(E1) - eval(E2) \ evaluate to numbers eval({* E1 E2}) = eval(E1) * eval(E2) / otherwise error! eval({/ E1 E2}) = eval(E1) / eval(E2) / eval(id) = error! eval({with {x E1} E2}) = eval(E2[eval(E1)/x]) eval(FUN) = FUN ; assuming FUN is a function expression eval({call E1 E2}) = eval(Ef[eval(E2)/x]) if eval(E1)={fun {x} Ef} = error! otherwise |# (define-type FLANG [Num (n Number)] [Add (lhs FLANG) (rhs FLANG)] [Sub (lhs FLANG) (rhs FLANG)] [Mul (lhs FLANG) (rhs FLANG)] [Div (lhs FLANG) (rhs FLANG)] [Id (name Symbol)] [With (name Symbol) (named FLANG) (body FLANG)] [Fun (name Symbol) (body FLANG)] [Call (fun-expr FLANG) (arg-expr FLANG)]) (: parse-sexpr : Sexpr -> FLANG) ;; to convert s-expressions into FLANGs (define (parse-sexpr sexpr) (match sexpr [(number: n) (Num n)] [(symbol: name) (Id name)] [(cons 'with more) (match sexpr [(list 'with (list (symbol: name) named) body) (With name (parse-sexpr named) (parse-sexpr body))] [else (error 'parse-sexpr "bad `with' syntax in ~s" sexpr)])] [(cons 'fun more) (match sexpr [(list 'fun (list (symbol: name)) body) (Fun name (parse-sexpr body))] [else (error 'parse-sexpr "bad `fun' syntax in ~s" sexpr)])] [(list '+ lhs rhs) (Add (parse-sexpr lhs) (parse-sexpr rhs))] [(list '- lhs rhs) (Sub (parse-sexpr lhs) (parse-sexpr rhs))] [(list '* lhs rhs) (Mul (parse-sexpr lhs) (parse-sexpr rhs))] [(list '/ lhs rhs) (Div (parse-sexpr lhs) (parse-sexpr rhs))] [(list 'call fun arg) (Call (parse-sexpr fun) (parse-sexpr arg))] [else (error 'parse-sexpr "bad syntax in ~s" sexpr)])) (: parse : String -> FLANG) ;; parses a string containing a FLANG expression to a FLANG AST (define (parse str) (parse-sexpr (string->sexpr str))) (: subst : FLANG Symbol FLANG -> FLANG) ;; substitutes the second argument with the third argument in the ;; first argument, as per the rules of substitution; the resulting ;; expression contains no free instances of the second argument (define (subst expr from to) (cases expr [(Num n) expr] [(Add l r) (Add (subst l from to) (subst r from to))] [(Sub l r) (Sub (subst l from to) (subst r from to))] [(Mul l r) (Mul (subst l from to) (subst r from to))] [(Div l r) (Div (subst l from to) (subst r from to))] [(Id name) (if (eq? name from) to expr)] [(With bound-id named-expr bound-body) (With bound-id (subst named-expr from to) (if (eq? bound-id from) bound-body (subst bound-body from to)))] [(Call l r) (Call (subst l from to) (subst r from to))] [(Fun bound-id bound-body) (if (eq? bound-id from) expr (Fun bound-id (subst bound-body from to)))])) (: arith-op : (Number Number -> Number) FLANG FLANG -> FLANG) ;; gets a Scheme numeric binary operator, and uses it within a FLANG ;; `Num' wrapper (define (arith-op op expr1 expr2) (: Num->number : FLANG -> Number) (define (Num->number e) (cases e [(Num n) n] [else (error 'arith-op "expects a number, got: ~s" e)])) (Num (op (Num->number expr1) (Num->number expr2)))) (: eval : FLANG -> FLANG) ;; evaluates FLANG expressions by reducing them to *expressions* (define (eval expr) (cases expr [(Num n) expr] [(Add l r) (arith-op + (eval l) (eval r))] [(Sub l r) (arith-op - (eval l) (eval r))] [(Mul l r) (arith-op * (eval l) (eval r))] [(Div l r) (arith-op / (eval l) (eval r))] [(With bound-id named-expr bound-body) (eval (subst bound-body bound-id (eval named-expr)))] [(Id name) (error 'eval "free identifier: ~s" name)] [(Fun bound-id bound-body) expr] [(Call (Fun bound-id bound-body) arg-expr) (eval (subst bound-body bound-id (eval arg-expr)))] [(Call something arg-expr) (error 'eval "`call' expects a function, got: ~s" something)])) (: run : String -> Number) ;; evaluate a FLANG program contained in a string (define (run str) (let ([result (eval (parse str))]) (cases result [(Num n) n] [else (error 'run "evaluation returned a non-number: ~s" result)]))) ;; tests (test (run "{call {fun {x} {+ x 1}} 4}") => 5) (test (run "{with {add3 {fun {x} {+ x 3}}} {call add3 1}}") => 4) (test (run "{with {add3 {fun {x} {+ x 3}}} {with {add1 {fun {x} {+ x 1}}} {with {x 3} {call add1 {call add3 x}}}}}") => 7) ---------------------------------------------------------------------- ========================================================================