PL: All notes  Tuesday, January 28th

Tuesday, January 7th Intro to CS4400/CS5400

Tuesday, January 7th Intro to Programming Languages

PLAI §1

Tuesday, January 7th Intro to Racket

Tuesday, January 7th Side-note: “Goto Statement Considered Harmful”

A review of “Goto Statement Considered Harmful”, by E.W. DIJKSTRA

This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don’t think that it will ever be eliminated), that programmers should not use it. It is not clear what should replace it. The paper doesn’t explain to us what would be the use of the if statement without a goto to redirect the flow of execution: Should all our postconditions consist of a single statement, or should we only use the arithmetic if, which doesn’t contain the offensive goto?

And how will one deal with the case in which, having reached the end of an alternative, the program needs to continue the execution somewhere else?

The author is a proponent of the so-called “structured programming” style, in which, if I get it right, gotos are replaced by indentation. Structured programming is a nice academic exercise, which works well for small examples, but I doubt that any real-world program will ever be written in such a style. More than 10 years of industrial experience with Fortran have proved conclusively to everybody concerned that, in the real world, the goto is useful and necessary: its presence might cause some inconveniences in debugging, but it is a de facto standard and we must live with it. It will take more than the academic elucubrations of a purist to remove it from our languages.

Publishing this would waste valuable paper: Should it be published, I am as sure it will go uncited and unnoticed as I am confident that, 30 years from now, the goto will still be alive and well and used as widely as it is today.

Confidential comments to the editor: The author should withdraw the paper and submit it someplace where it will not be peer reviewed. A letter to the editor would be a perfect choice: Nobody will notice it there!

Tuesday, January 7th Quick Intro to Racket

Racket syntax: similar to other Sexpr-based languages.

Reminder: the parens can be compared to C/etc function call parens — they always mean that some function is applied. This is the reason why (+ (1) (2)) won’t work: if you use C syntax that is +(1(), 2()) but 1 isn’t a function so 1() is an error.

An important difference between syntax and semantics: A good way to think about this is the difference between the string 42 stored in a file somewhere (two ASCII values), and the number 42 stored in memory (in some representation). You could also continue with the above example: there is nothing wrong with “murder” — it’s just a word, but murder is something you’ll go to jail for.

The evaluation function that Racket uses is actually a function that takes a piece of syntax and returns (or executes) its semantics.


define expressions are used for creating new bindings, do not try to use them to change values. For example, you should not try to write something like (define x (+ x 1)) in an attempt to mimic x = x+1. It will not work.


There are two boolean values built in to Racket: #t (true) and #f (false). They can be used in if statements, for example:

(if (< 2 3) 10 20)  -->  10

because (< 2 3) evaluates to #t. As a matter of fact, any value except for #f is considered to be true, so:

(if 0 1 2)        -->  1  ; all of these are "truthy"
(if "false" 1 2)  -->  1
(if "" 1 2)      -->  1
(if null 1 2)    -->  1
(if #t 1 2)      -->  1  ; including the true value
(if #f 1 2)      -->  2  ; the only false value
(if #false 1 2)  -->  2  ; another way to write it
(if false 1 2)    -->  2  ; also false since it's bound to #f

Note: Racket is a functional language — so everything has a value.

This means that the expression

(if test consequent)

has no meaning when test evaluates to #f. This is unlike Pascal/C where statements do something (side effect) like printing or an assignment — here an if statement with no alternate part will just do nothing if the test is false… Racket, however, must return some value — it could decide on simply returning #f (or some unspecified value) as the value of

(if #f something)

as some implementations do, but Racket just declares it a syntax error. (As we will see in the future, Racket has a more convenient when with a clearer intention.)


Well, almost everything is a value…

There are certain things that are part of Racket’s syntax — for example if and define are special forms, they do not have a value! More about this shortly.

(Bottom line: much more things do have a value, compared with other languages.)


cond is used for a ifelse ifelse ifelse … sequence. The problem is that nested ifs are inconvenient. For example,

(define (digit-num n)
  (if (<= n 9)
    1
    (if (<= n 99)
      2
      (if (<= n 999)
        3
        (if (<= n 9999)
          4
          "a lot")))))

In C/Java/Whatever, you’d write:

function digit_num(n) {
  if (n <= 9)        return 1;
  else if (n <= 99)  return 2;
  else if (n <= 999)  return 3;
  else if (n <= 9999) return 4;
  else return "a lot";
}

(Side question: why isn’t there a return statement in Racket?)

But trying to force Racket code to look similar:

(define (digit-num n)
  (if (<= n 9)
    1
  (if (<= n 99)
    2
  (if (<= n 999)
    3
  (if (<= n 9999)
    4
    "a lot")))))

is more than just bad taste — the indentation rules are there for a reason, the main one is that you can see the structure of your program at a quick glance, and this is no longer true in the above code. (Such code will be penalized!)

So, instead of this, we can use Racket’s cond statement, like this:

(define (digit-num n)
  (cond [(<= n 9)    1]
        [(<= n 99)  2]
        [(<= n 999)  3]
        [(<= n 9999) 4]
        [else        "a lot"]))

Note that else is a keyword that is used by the cond form — you should always use an else clause (for similar reasons as an if, to avoid an extra expression evaluation there, and we will need it when we use a typed language). Also note that square brackets are read by DrRacket like round parens, it will only make sure that the paren pairs match. We use this to make code more readable — specifically, there is a major difference between the above use of [] from the conventional use of (). Can you see what it is?

The general structure of a cond:

(cond [test-1 expr-1]
      [test-2 expr-2]
      ...
      [test-n expr-n]
      [else  else-expr])

Example for using an if expression, and a recursive function:

(define (fact n)
  (if (zero? n)
    1
    (* n (fact (- n 1)))))

Use this to show the different tools, especially:

An example of converting it to tail recursive form:

(define (helper n acc)
  (if (zero? n)
    acc
    (helper (- n 1) (* acc n))))

(define (fact n)
  (helper n 1))

Additional notes about homework submissions:

Tuesday, January 7th Lists & Recursion

Lists are a fundamental Racket data type.

A list is defined as either:

  1. the empty list (null, empty, or '()),

  2. a pair (cons cell) of anything and a list.

As simple as this may seem, it gives us precise formal rules to prove that something is a list.

Examples:

null
(cons 1 null)
(cons 1 (cons 2 (cons 3 null)))
(list 1 2 3) ; a more convenient function to get the above

List operations — predicates:

null? ; true only for the empty list
pair? ; true for any cons cell
list? ; this can be defined using the above

We can derive list? from the above rules:

(define (list? x)
  (if (null? x)
    #t
    (and (pair? x) (list? (rest x)))))

or better:

(define (list? x)
  (or (null? x)
      (and (pair? x) (list? (rest x)))))

But why can’t we define list? more simply as

(define (list? x)
  (or (null? x) (pair? x)))

The difference between the above definition and the proper one can be observed in the full Racket language, not in the student languages (where there are no pairs with non-list values in their tails).

List operations — destructors for pairs (cons cells):

first
rest

Traditionally called car, cdr.

Also, any c<x>r combination for <x> that is made of up to four as and/or ds — we will probably not use much more than cadr, caddr etc.


Example for recursive function involving lists:

(define (list-length list)
  (if (null? list)
    0
    (+ 1 (list-length (rest list)))))

Use different tools, esp:

How come we could use list as an argument — use the syntax checker

(define (list-length-helper list len)
  (if (null? list)
    len
    (list-length-helper (rest list) (+ len 1))))

(define (list-length list)
  (list-length-helper list 0))

Main idea: lists are a recursive structure, so functions that operate on lists should be recursive functions that follow the recursive definition of lists.

Another example for list function — summing a list of numbers

(define (sum-list l)
  (if (null? l)
    0
    (+ (first l) (sum-list (rest l)))))

Also show how to implement rcons, using this guideline.


More examples:

Define reverse — solve the problem using rcons.

rcons can be generalized into something very useful: append.

Redefine reverse using tail recursion.

Tuesday, January 7th Some Style

When you have some common value that you need to use in several places, it is bad to duplicate it. For example:

(define (how-many a b c)
  (cond [(> (* b b) (* 4 a c)) 2]
        [(= (* b b) (* 4 a c)) 1]
        [(< (* b b) (* 4 a c)) 0]))

What’s bad about it?

In general, the ability to use names is probably the most fundamental concept in computer science — the fact that makes computer programs what they are.

We already have a facility to name values: function arguments. We could split the above function into two like this:

(define (how-many-helper b^2 4ac) ; note: valid names!
  (cond [(> b^2 4ac) 2]
        [(= b^2 4ac) 1]
        [else        0]))

(define (how-many a b c)
  (how-many-helper (* b b) (* 4 a c)))

But instead of the awkward solution of coming up with a new function just for its names, we have a facility to bind local names — let. In general, the syntax for a let special form is

(let ([id expr] ...) expr)

For example,

(let ([x 1] [y 2]) (+ x y))

But note that the bindings are done “in parallel”, for example, try this:

(let ([x 1] [y 2])
  (let ([x y] [y x])
    (list x y)))

(Note that “in parallel” is quoted here because it’s not really parallelism, but just a matter of scopes: the RHSs are all evaluated in the surrounding scope!)

Using this for the above problem:

(define (how-many a b c)
  (let ([b^2 (* b b)]
        [4ac (* 4 a c)])
    (cond [(> b^2 4ac) 2]
          [(= b^2 4ac) 1]
          [else        0])))

Tuesday, January 7th Tail calls

You should generally know what tail calls are, but here’s a quick review of the subject. A function call is said to be in tail position if there is no context to “remember” when you’re calling it. Very roughly, this means that function calls that are not nested as argument expressions of another call are tail calls. Pay attention that we’re talking about function calls, not, for example, being nested in an if expression since that’s not a function. (The same holds for cond, and, or.)

This definition is something that depends on the context, for example, in an expression like

(if (huh?)
  (foo (add1 (* x 3)))
  (foo (/ x 2)))

both calls to foo are tail calls, but they’re tail calls of this expression and therefore apply to this context. It might be that this code is inside another call, as in

(blah (if (huh?)
        (foo (add1 (* x 3)))
        (foo (/ x 2)))
      something-else)

and the foo calls are now not in tail position. The main feature of all Scheme implementations including Racket (and including Javascript) WRT tail calls is that calls that are in tail position of a function are said to be “eliminated”. That means that if we’re in an f function, and we’re about to call g in tail position and therefore whatever g returns would be the result of f too, then when Racket does the call to g it doesn’t bother keeping the f context — it won’t remember that it needs to “return” to f and will instead return straight to its caller. In other words, when you think about a conventional implementation of function calls as frames on a stack, Racket will get rid of a stack frame when it can.

You can also try this with any code in DrRacket: hovering over the paren that starts a function call will show a faint pinkish arrow showing the tail-call chain from there for call that are actually tail calls. This is a simple feature since tail calls are easily identifiable by just looking at the syntax of a function.

Another way to see this is to use DrRacket’s stepper to step through a function call. The stepper is generally an alternative debugger, where instead of visualizing stack frames it assembles an expression that represents these frames. Now, in the case of tail calls, there is no room in such a representation to keep the call — and the thing is that in Racket that’s perfectly fine since these calls are not kept on the call stack.

Note that there are several names for this feature:

When should you use tail calls?

Often, people who are aware of tail calls will try to use them always. That’s not always a good idea. You should generally be aware of the tradeoffs when you consider what style to use. The main thing to remember is that tail-call elimination is a property that helps reducing space use (stack space) — often reducing it from linear space to constant space. This can obviously make things faster, but usually the speedup is just a constant factor since you need to do the same number of iterations anyway, so you just reduce the time spent on space allocation.

Here is one such example that we’ve seen:

(define (list-length-1 list)
  (if (null? list)
    0
    (+ 1 (list-length-1 (rest list)))))

;; versus

(define (list-length-helper list len)
  (if (null? list)
    len
    (list-length-helper (rest list) (+ len 1))))
(define (list-length-2 list)
  (list-length-helper list 0))

In this case the first (recursive) version version consumes space linear to the length of the list, whereas the second version needs only constant space. But if you consider only the asymptotic runtime, they are both O(length(l)).

A second example is a simple implementation of map:

(define (map-1 f l)
  (if (null? l) l (cons (f (first l)) (map-1 f (rest l)))))

;; versus

(define (map-helper f l acc)
  (if (null? l)
    (reverse acc)
    (map-helper f (rest l) (cons (f (first l)) acc))))
(define (map-2 f l)
  (map-helper f l '()))

In this case, both the asymptotic space and the runtime consumption are the same. In the recursive case we have a constant factor for the stack space, and in the iterative one (the tail-call version) we also have a similar factor for accumulating the reversed list. In this case, it is probably better to keep the first version since the code is simpler. In fact, Racket’s stack space management can make the first version run faster than the second — so optimizing it into the second version is useless.

Tuesday, January 7th Sidenote on Types

Note: this is all just a side note for a particularly hairy example. You don’t need to follow all of this to write code in this class! Consider this section a kind of an extra type-related puzzle to read trough, and maybe get back to it much later, after we cover typechecking.

Types can become interestingly complicated when dealing with higher-order functions. Specifically, the nature of the type system used by Typed Racket makes it have one important weakness: it often fails to infer types when there are higher-order functions that operate on polymorphic functions.

For example, consider how map receives a function and a list of some type, and applies the function over this list to accumulate its output, so it’s a polymorphic function with the following type:

map : (A -> B) (Listof A) -> (Listof B)

But Racket’s map is actually more flexible that that: it can take more than a single list input, in which case it will apply the function on the first element in all lists, then the second and so on. Narrowing our vision to the two-input-lists case, the type of map then becomes:

map : (A B -> C) (Listof A) (Listof B) -> (Listof C)

Now, here’s a hairy example — what is the type of this function:

(define (foo x y)
  (map map x y))

Begin by what we know — both maps, call them map1 and map2, have the double- and single-list types of map respectively, here they are, with different names for types:

;; the first `map', consumes a function and two lists
map1 : (A B -> C) (Listof A) (Listof B) -> (Listof C)

;; the second `map', consumes a function and one list
map2 : (X -> Y) (Listof X) -> (Listof Y)

Now, we know that map2 is the first argument to map1, so the type of map1s first argument should be the type of map2:

(A B -> C) = (X -> Y) (Listof X) -> (Listof Y)

From here we can conclude that

A = (X -> Y)

B = (Listof X)

C = (Listof Y)

If we use these equations in map1’s type, we get:

map1 : ((X -> Y) (Listof X) -> (Listof Y))
      (Listof (X -> Y))
      (Listof (Listof X))
      -> (Listof (Listof Y))

Now, foo’s two arguments are the 2nd and 3rd arguments of map1, and its result is map1s result, so we can now write our “estimated” type of foo:

(: foo : (Listof (X -> Y))
        (Listof (Listof X))
        -> (Listof (Listof Y)))
(define (foo x y)
  (map map x y))

This should help you understand why, for example, this will cause a type error:

(foo (list add1 sub1 add1) (list 1 2 3))

and why this is valid:

(foo (list add1 sub1 add1) (map list (list 1 2 3)))

But…!

There’s a big “but” here which is that weakness of Typed Racket that was mentioned. If you try to actually write such a defninition in #lang pl (which is based on Typed Racket), you will first find that you need to explicitly list the type variable that are needed to make it into a generic type. So the above becomes:

(: foo : (All (X Y)
          (Listof (X -> Y))
          (Listof (Listof X))
          -> (Listof (Listof Y))))
(define (foo x y)
  (map map x y))

But not only does that not work — it throws an obscure type error. That error is actually due to TR’s weakness: it’s a result of not being able to infer the proper types. In such cases, TR has two mechanisms to “guide it” in the right direction. The first one is inst, which is used to instantiate a generic (= polymorphic) type some actual type. The problem here is with the second map since that’s the polymorphic function that is given to a higher-order function (the first map). If we provide the types to instantiate this, it will work fine:

(: foo : (All (X Y)
          (Listof (X -> Y))
          (Listof (Listof X))
          -> (Listof (Listof Y))))
(define (foo x y)
  (map (inst map Y X) x y))

Now, you can use this definition to run the above example:

(foo (list add1 sub1 add1) (list (list 1) (list 2) (list 3)))

This example works fine, but that’s because we wrote the list argument explicitly. If you try to use the exact example above,

(foo (list add1 sub1 add1) (map list (list 1 2 3)))

you’d run into the same problem again, since this also uses a polymorphic function (list) with a higher-order one (map). Indeed, an inst can make this work for this too:

(foo (list add1 sub1 add1) (map (inst list Number) (list 1 2 3)))

The second facility is ann, which can be used to annotate an expression with the type that you expect it to have.

(define (foo x y)
  (map (ann map ((X -> Y) (Listof X) -> (Listof Y)))
      x y))

(Note: this is not type casting! It’s using a different type which is also applicable for the given expression, and having the type checker validate that this is true. TR does have a similar cast form, which is used for a related but different cases.)

This tends to be more verbose than inst, but is sometimes easier to follow, since the expected type is given explicitly. The thing about inst is that it’s kind of “applying” a polymorphic (All (A B) ...) type, so you need to know the order of the A B arguments, which is why in the above we use (inst map Y X) rather than (inst map X Y).

Again, remember that this is all not something that you need to know. We will have a few (very rare) cases where we’ll need to use inst, and in each of these, you’ll be told where and how to use it.

Tuesday, January 7th Side-note: Names are important

An important “discovery” in computer science is that we don’t need names for every intermediate sub-expression — for example, in almost any language we can write something like:

s = (-b + sqrt(b^2 - 4*a*c)) / (2*a)

instead of

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

Such languages are put in contrast to assembly languages, and were all put under the generic label of “high level languages”.

(Here’s an interesting idea — why not do the same for function values?)

Tuesday, January 14th BNF, Grammars, the AE Language

Getting back to the theme of the course: we want to investigate programming languages, and we want to do that using a programming language.

The first thing when we design a language is to specify the language. For this we use BNF (Backus-Naur Form). For example, here is the definition of a simple arithmetic language:

<AE> ::= <num>
      | <AE> + <AE>
      | <AE> - <AE>

Explain the different parts. Specifically, this is a mixture of low-level (concrete) syntax definition with parsing.

We use this to derive expressions in some language. We start with <AE>, which should be one of these:

<num> is a terminal: when we reach it in the derivation, we’re done. <AE> is a non-terminal: when we reach it, we have to continue with one of the options. It should be clear that the + and the - are things we expect to find in the input — because they are not wrapped in <>s.

We could specify what <num> is (turning it into a <NUM> non-terminal):

<AE> ::= <NUM>
      | <AE> + <AE>
      | <AE> - <AE>

<NUM> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
        | <NUM> <NUM>

But we don’t — why? Because in Racket we have numbers as primitives and we want to use Racket to implement our languages. This makes life a lot easier, and we get free stuff like floats, rationals etc.

To use a BNF formally, for example, to prove that 1-2+3 is a valid <AE> expression, we first label the rules:

<AE> ::= <num>        (1)
      | <AE> + <AE>  (2)
      | <AE> - <AE>  (3)

and then we can use them as formal justifications for each derivation step:

<AE>
<AE> + <AE>        ; (2)
<AE> + <num>        ; (1)
<AE> - <AE> + <num> ; (3)
<AE> - <AE> + 3    ; (num)
<num> - <AE> + 3    ; (1)
<num> - <num> + 3  ; (1)
1 - <num> + 3      ; (num)
1 - 2 + 3          ; (num)

This would be one way of doing this. Alternatively, we can can visualize the derivation using a tree, with the rules used at the nodes.

These specifications suffer from being ambiguous: an expression can be derived in multiple ways. Even the little syntax for a number is ambiguous — a number like 123 can be derived in two ways that result in trees that look different. This ambiguity is not a “real” problem now, but it will become one very soon. We want to get rid of this ambiguity, so that there is a single (= deterministic) way to derive all expressions.

There is a standard way to resolve that — we add another non-terminal to the definition, and make it so that each rule can continue to exactly one of its alternatives. For example, this is what we can do with numbers:

<NUM>  ::= <DIGIT> | <DIGIT> <NUM>

<DIGIT> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Similar solutions can be applied to the <AE> BNF — we either restrict the way derivations can happen or we come up with new non-terminals to force a deterministic derivation trees.

As an example of restricting derivations, we look at the current grammar:

<AE> ::= <num>
      | <AE> + <AE>
      | <AE> - <AE>

and instead of allowing an <AE> on both sides of the operation, we force one to be a number:

<AE> ::= <num>
      | <num> + <AE>
      | <num> - <AE>

Now there is a single way to derive any expression, and it is always associating operations to the right: an expression like 1+2+3 can only be derived as 1+(2+3). To change this to left-association, we would use this:

<AE> ::= <num>
      | <AE> + <num>
      | <AE> - <num>

But what if we want to force precedence? Say that our AE syntax has addition and multiplication:

<AE> ::= <num>
      | <AE> + <AE>
      | <AE> * <AE>

We can do that same thing as above and add new non-terminals — say one for “products”:

<AE>  ::= <num>
        | <AE> + <AE>
        | <PROD>

<PROD> ::= <num>
        | <PROD> * <PROD>

Now we must parse any AE expression as additions of multiplications (or numbers). First, note that if <AE> goes to <PROD> and that goes to <num>, then there is no need for an <AE> to go to a <num>, so this is the same syntax:

<AE>  ::= <AE> + <AE>
        | <PROD>

<PROD> ::= <num>
        | <PROD> * <PROD>

Now, if we want to still be able to multiply additions, we can force them to appear in parentheses:

<AE>  ::= <AE> + <AE>
        | <PROD>

<PROD> ::= <num>
        | <PROD> * <PROD>
        | ( <AE> )

Next, note that <AE> is still ambiguous about additions, which can be fixed by forcing the left hand side of an addition to be a factor:

<AE>  ::= <PROD> + <AE>
        | <PROD>

<PROD> ::= <num>
        | <PROD> * <PROD>
        | ( <AE> )

We still have an ambiguity for multiplications, so we do the same thing and add another non-terminal for “atoms”:

<AE>  ::= <PROD> + <AE>
        | <PROD>

<PROD> ::= <ATOM> * <PROD>
        | <ATOM>

<ATOM> ::= <num>
        | ( <AE> )

And you can try to derive several expressions to be convinced that derivation is always deterministic now.

But as you can see, this is exactly the cosmetics that we want to avoid — it will lead us to things that might be interesting, but unrelated to the principles behind programming languages. It will also become much much worse when we have a real language rather such a tiny one.

Is there a good solution? — It is right in our face: do what Racket does — always use fully parenthesized expressions:

<AE> ::= <num>
      | ( <AE> + <AE> )
      | ( <AE> - <AE> )

To prevent confusing Racket code with code in our language(s), we also change the parentheses to curly ones:

<AE> ::= <num>
      | { <AE> + <AE> }
      | { <AE> - <AE> }

But in Racket everything has a value — including those +s and -s, which makes this extremely convenient with future operations that might have either more or less arguments than 2 as well as treating these arithmetic operators as plain functions. In our toy language we will not do this initially (that is, + and - are second order operators: they cannot be used as values). But since we will get to it later, we’ll adopt the Racket solution and use a fully-parenthesized prefix notation:

<AE> ::= <num>
      | { + <AE> <AE> }
      | { - <AE> <AE> }

(Remember that in a sense, Racket code is written in a form of already-parsed syntax…)

Tuesday, January 14th Simple Parsing

On to an implementation of a “parser”:

Unrelated to what the syntax actually looks like, we want to parse it as soon as possible — converting the concrete syntax to an abstract syntax tree.

No matter how we write our syntax:

we always mean the same abstract thing — adding the number 3 and the number 4. The essence of this is basically a tree structure with an addition operation as the root and two leaves holding the two numerals.

With the right data definition, we can describe this in Racket as the expression (Add (Num 3) (Num 4)) where Add and Num are constructors of a tree type for syntax, or in a C-like language, it could be something like Add(Num(3),Num(4)).

Similarly, the expression (3-4)+7 will be described in Racket as the expression:

(Add (Sub (Num 3) (Num 4)) (Num 7))

Important note: “expression” was used in two different ways in the above — each way corresponds to a different language, and the result of evaluating the second “expression” is a Racket value that represents the first expression.

To define the data type and the necessary constructors we will use this:

(define-type AE
  [Num Number]
  [Add AE AE]
  [Sub AE AE])

To make things very simple, we will use the above fact through a double-level approach:

This is achieved by the following simple recursive function:

(: parse-sexpr : Sexpr -> AE)
;; parses s-expressions into AEs
(define (parse-sexpr sexpr)
  (cond [(number? sexpr) (Num sexpr)]
        [(and (list? sexpr) (= 3 (length sexpr)))
        (let ([make-node
                (match (first sexpr)
                  ['+ Add]
                  ['- Sub]
                  [else (error 'parse-sexpr "unknown op: ~s"
                              (first sexpr))])
                #| the above is the same as:
                (cond [(equal? '+ (first sexpr)) Add]
                      [(equal? '- (first sexpr)) Sub]
                      [else (error 'parse-sexpr "unknown op: ~s"
                                  (first sexpr))])
                |#])
          (make-node (parse-sexpr (second sexpr))
                      (parse-sexpr (third sexpr))))]
        [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

This function is pretty simple, but as our languages grow, they will become more verbose and more difficult to write. So, instead, we use a new special form: match, which is matching a value and binds new identifiers to different parts (try it with “Check Syntax”). Re-writing the above code using match:

(: parse-sexpr : Sexpr -> AE)
;; parses s-expressions into AEs
(define (parse-sexpr sexpr)
  (match sexpr
    [(number: n) (Num n)]
    [(list '+ left right)
    (Add (parse-sexpr left) (parse-sexpr right))]
    [(list '- left right)
    (Sub (parse-sexpr left) (parse-sexpr right))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

And finally, to make it more uniform, we will combine this with the function that parses a string into a sexpr so we can use strings to represent our programs:

(: parse : String -> AE)
;; parses a string containing an AE expression to an AE
(define (parse str)
  (parse-sexpr (string->sexpr str)))

Tuesday, January 14th The match Form

The syntax for match is

(match value
  [pattern result-expr]
  ...)

The value is matched against each pattern, possibly binding names in the process, and if a pattern matches it evaluates the result expression. The simplest form of a pattern is simply an identifier — it always matches and binds that identifier to the value:

(match (list 1 2 3)
  [x x]) ; evaluates to the list

Another simple pattern is a quoted symbol, which matches that symbol. For example:

(match foo
  ['x "yes"]
  [else "no"])

will evaluate to "yes" if foo is the symbol x, and to "no" otherwise. Note that else is not a keyword here — it happens to be a pattern that always succeeds, so it behaves like an else clause except that it binds else to the unmatched-so-far value.

Many patterns look like function application — but don’t confuse them with applications. A (list x y z) pattern matches a list of exactly three items and binds the three identifiers; or if the “arguments” are themselves patterns, match will descend into the values and match them too. More specifically, this means that patterns can be nested:

(match (list 1 2 3)
  [(list x y z) (+ x y z)]) ; evaluates to 6
(match (list 1 2 3)
  [(cons x (list y z)) (+ x y z)]) ; matches the same shape (also 6)
(match '((1) (2) 3)
  [(list (list x) (list y) z) (+ x y z)]) ; also 6

As seen above, there is also a cons pattern that matches a non-empty list and then matches the first part against the head for the list and the second part against the tail of the list.

In a list pattern, you can use ... to specify that the previous pattern is repeated zero or more times, and bound names get bound to the list of respective matching. One simple consequent is that the (list hd tl ...) pattern is exactly the same as (cons hd tl), but being able to repeat an arbitrary pattern is very useful:

> (match '((1 2) (3 4) (5 6) (7 8))
    [(list (list x y) ...) (list x y)])
'((1 3 5 7) (2 4 6 8))

A few more useful patterns:

id              -- matches anything, binds `id' to it
_              -- matches anything, but does not bind
(number: n)    -- matches any number and binds it to `n'
(symbol: s)    -- same for symbols
(string: s)    -- strings
(sexpr: s)      -- S-expressions (needed sometimes for Typed Racket)
(and pat1 pat2) -- matches both patterns
(or pat1 pat2)  -- matches either pattern (careful with bindings)

Note that the foo: patterns are all specific to our #lang pl, they are not part of #lang racket or #lang typed/racket.

The patterns are tried one by one in-order, and if no pattern matches the value, an error is raised.

Note that ... in a list pattern can follow any pattern, including all of the above, and including nested list patterns.

Here are a few examples — you can try them out with #lang pl untyped at the top of the definitions window. This:

(match x
  [(list (symbol: syms) ...) syms])

matches x against a pattern that accepts only a list of symbols, and binds syms to those symbols. If you want to match only a list of, say, one or more symbols, then just add one before the ...-ed pattern variable:

(match x
  [(list (symbol: sym) (symbol: syms) ...) syms])
;; same as:
(match x
  [(cons (symbol: sym) (list (symbol: syms) ...)) syms])

which will match such a non-empty list, where the whole list (on the right hand side) is (cons sym syms).

Here’s another example that matches a list of any number of lists, where each of the sub-lists begins with a symbol and then has any number of numbers. Note how the n and s bindings get values for a list of all symbols and a list of lists of the numbers:

> (define (foo x)
    (match x
      [(list (list (symbol: s) (number: n) ...) ...)
      (list 'symbols: s 'numbers: n)]))
> (foo (list (list 'x 1 2 3) (list 'y 4 5)))
'(symbols: (x y) numbers: ((1 2 3) (4 5)))

Here is a quick example for how or is used with two literal alternatives, how and is used to name a specific piece of data, and how or is used with a binding:

> (define (foo x)
    (match x
      [(list (or 1 2 3)) 'single]
      [(list (and x (list 1 _)) 2) x]
      [(or (list 1 x) (list 2 x)) x]))
> (foo (list 3))
'single
> (foo (list (list 1 99) 2))
'(1 99)
> (foo (list 1 10))
10
> (foo (list 2 10))
10

Tuesday, January 14th The define-type Form

The class language that we’re using, #lang pl, is based on Typed Racket: a statically-typed dialect of Racket. It is not exactly the same as Typed Racket — it is restricted in many ways, and extended in a few ways. (You should therefore try to avoid looking at the Typed Racket documentation and expect things to be the same in #lang pl.)

The most important extension is define-type, which is the construct we will be using to create new user-defined types. In general, such definitions looks like what we just used:

(define-type AE
  [Num Number]
  [Add AE AE]
  [Sub AE AE])

This defines a new type called AE, an AE? predicate for this type, and a few variants for this type: Num, Add, and Sub in this case. Each of these variant names is a constructor, taking in arguments with the listed types, where these types can include the newly defined type itself in (the very common) case we’re defining a recursive type. The return type is always the newly defined type, AE here.

To summarize, this definition gives us a new AE type, and three constructors, as if we wrote the following type declarations:

The newly defined types are known as “disjoint unions”, since values in these types are disjoint — there is no overlap between the different variants. As we will see, this is what makes this such a useful construct for our needs: the compiler knows about the variants of each newly defined type, which will make it possible for it to complain if we extend a type with more variants but not update all uses of the type.

Furthermore, since the return types of these constructors are all the new type itself, there is no way for us to write code that expects just one of these variants. We will use a second form, cases, to handle these values.

Tuesday, January 14th The cases Form

A define-type declaration defines only what was described above: one new type name and a matching predicate, and a few variants as constructor functions. Unlike HtDP, we don’t get predicates for each of the variants, and we don’t get accessor functions for the fields of the variants.

The way that we handle the new kind of values is with cases: this is a form that is very similar to match, but is specific to instances of the user-defined type.

Many students find it confusing to distinguish match and cases since they are so similar. Try to remember that match is for primitive Racket values (we’ll mainly use them for S-expression values), while cases is for user-defined values. The distinction between the two forms is unfortunate, and doesn’t serve any purpose. It is just technically difficult to unify the two.

For example, code that handles AE values (as defined above) can look as follows:

(cases some-ae-value
  [(Num n)  "a number"]
  [(Add l r) "an addition"]
  [(Sub l r) "a subtraction"])

As you can see, we need to have patterns for each of the listed variants (and the compiler will throw an error if some are missing), and each of these patterns specifies bindings that will get the field values contained in a given variant object.

We can also use nested patterns:

(cases some-ae-value
  [(Num n)              "a number"]
  [(Add (Num m) (Num n)) "a simple addition"]
  [(Add l r)            "an addition"]
  [(Sub (Num m) (Num n)) "a simple subtraction"]
  [(Sub l r)            "a subtraction"])

but this is a feature that we will not use too often.

The final clause in a cases form can be an else clause, which serves as a fallback in case none of the previous clauses matched the input value. However, using an else like this is strongly discouraged! The problem with using it is that it effectively eliminates the advantage in getting the type-checker to complain when a type definition is extended with new variants. Using these else clauses, we can actually mimic all of the functionality that you expect in HtDP-style code, which demonstrates that this is equivalent to HtDP-style definitions. For example:

(: Add? : AE -> Boolean)
;; identifies instances of the `Add` variant
(define (Add? ae)
  (cases ae
    [(Add l r) #t]
    [else #f]))

(: Add-left : AE -> AE)
;; get the left-hand subexpression of an addition
(define (Add-left ae)
  (cases ae
    [(Add l r) l]
    [else (error 'Add-left "expecting an Add value, got ~s" ae)]))

...

Important reminder: this is code that you should not write! Doing so will lead to code that is more fragile than just using cases, since you’d be losing the protection the compiler gives you in the form of type errors on occurrences of cases that need to be updated when a type is extended with new variants. You would therefore end up writing a bunch of boiler-plate code only to end up with lower-quality code. The core of the problem is in the prevalent use of else which gives up that protection.

In these examples the else clause is justified because even if AE is extended with new variants, functions like Add? and Add-left should not be affected and treat the new variants as they treat all other non-Add instances. (And since else is inherent to these functions, using them in our code is inherently a bad idea.) We will, however, have a few (very few!) places where we’ll need to use else — but this will always be done only on some specific functionality rather than a wholesale approach of defining a different interface for user-defined types.

Tuesday, January 14th Semantics (= Evaluation)

PLAI §2

Back to BNF — now, meaning.

An important feature of these BNF specifications: we can use the derivations to specify meaning (and meaning in our context is “running” a program (or “interpreting”, “compiling”, but we will use “evaluating”)). For example:

<AE> ::= <num>        ; <AE> evaluates to the number
      | <AE1> + <AE2> ; <AE> evaluates to the sum of evaluating
                      ;      <AE1> and <AE2>
      | <AE1> - <AE2> ; ... the subtraction of <AE2> from <AE1>
                              (... roughly!)

To do this a little more formally:

a. eval(<num>) = <num> ;*** special rule: translate syntax to value
b. eval(<AE1> + <AE2>) = eval(<AE1>) + eval(<AE2>)
c. eval(<AE1> - <AE2>) = eval(<AE1>) - eval(<AE2>)

Note the completely different roles of the two +s and -s. In fact, it might have been more correct to write:

a. eval("<num>") = <num>
b. eval("<AE1> + <AE2>") = eval("<AE1>") + eval("<AE2>")
c. eval("<AE1> - <AE2>") = eval("<AE1>") - eval("<AE2>")

or even using a marker to denote meta-holes in these strings:

a. eval("$<num>") = <num>
b. eval("$<AE1> + $<AE2>") = eval("$<AE1>") + eval("$<AE2>")
c. eval("$<AE1> - $<AE2>") = eval("$<AE1>") - eval("$<AE2>")

but we will avoid pretending that we’re doing that kind of string manipulation. (For example, it will require specifying what does it mean to return <num> for $<num> (involves string->number), and the fragments on the right side mean that we need to specify these as substring operations.)

Note that there’s a similar kind of informality in our BNF specifications, where we assume that <foo> refers to some terminal or non-terminal. In texts that require more formal specifications (for example, in RFC specifications), each literal part of the BNF is usually double-quoted, so we’d get

<AE> ::= <num> | <AE1> "+" <AE2> | <AE1> "-" <AE2>

An alternative popular notation for eval(X) is ⟦X⟧:

a. [[<num>]] = <num>
b. [[<AE1> + <AE2>]] = [[<AE1>]] + [[<AE2>]]
c. [[<AE1> - <AE2>]] = [[<AE1>]] - [[<AE2>]]

Is there a problem with this definition? Ambiguity:

eval(1 - 2 + 3) = ?

Depending on the way the expression is parsed, we can get either a result of 2 or -4:

eval(1 - 2 + 3) = eval(1 - 2) + eval(3)          [b]
                = eval(1) - eval(2) + eval(3)    [c]
                = 1 - 2 + 3                      [a,a,a]
                = 2

eval(1 - 2 + 3) = eval(1) - eval(2 + 3)          [c]
                = eval(1) - (eval(2) + eval(3))  [a]
                = 1 - (2 + 3)                    [a,a,a]
                = -4

Again, be very aware of confusing subtleties which are extremely important: We need parens around a sub-expression only in one side, why? — When we write:

eval(1 - 2 + 3) = ... = 1 - 2 + 3

we have two expressions, but one stands for an input syntax, and one stands for a real mathematical expression.

In a case of a computer implementation, the syntax on the left is (as always) an AE syntax, and the real expression on the right is an expression in whatever language we use to implement our AE language.

Like we said earlier, ambiguity is not a real problem until the actual parse tree matters. With eval it definitely matters, so we must not make it possible to derive any syntax in multiple ways or our evaluation will be non-deterministic.


Quick exercise:

We can define a meaning for <digit>s and then <num>s in a similar way:

<NUM> ::= <digit> | <digit> <NUM>

eval(0) = 0
eval(1) = 1
eval(2) = 2
...
eval(9) = 9

eval(<digit>) = <digit>
eval(<digit> <NUM>) = 10*eval(<digit>) + eval(<NUM>)

Is this exactly what we want? — Depends on what we actually want…

Tuesday, January 14th Side-note: Compositionality

The example of

<NUM> ::= <digit> | <NUM> <digit>

being a language that is easier to write an evaluator for leads us to an important concept — compositionality. This definition is easier to write an evaluator for, since the resulting language is compositional: the meaning of an expression — for example 123 — is composed out of the meaning of its two parts, which in this BNF are 12 and 3. Specifically, the evaluation of <NUM> <digit> is 10 * the evaluation of the first, plus the evaluation of the second. In the <digit> <NUM> case this is more difficult — the meaning of such a number depends not only on the meaning of the two parts, but also on the <NUM> syntax:

eval(<digit> <NUM>) =
  eval(<digit>) * 10^length(<NUM>) + eval(<NUM>)

This this case this can be tolerable, since the meaning of the expression is still made out of its parts — but imperative programming (when you use side effects) is much more problematic since it is not compositional (at least not in the obvious sense). This is compared to functional programming, where the meaning of an expression is a combination of the meanings of its subexpressions. For example, every sub-expression in a functional program has some known meaning, and these all make up the meaning of the expression that contains them — but in an imperative program we can have a part of the code be x++ — and that doesn’t have a meaning by itself, at least not one that contributes to the meaning of the whole program in a direct way.

(Actually, we can have a well-defined meaning for such an expression: the meaning is going from a world where x is a container of some value N, to a world where the same container has a different value N+1. You can probably see now how this can make things more complicated. On an intuitive level — if we look at a random part of a functional program we can tell its meaning, so building up the meaning of the whole code is easy, but in an imperative program, the meaning of a random part is pretty much useless.)

Tuesday, January 14th Implementing an Evaluator

Now continue to implement the semantics of our syntax — we express that through an eval function that evaluates an expression.

We use a basic programming principle — splitting the code into two layers, one for parsing the input, and one for doing the evaluation. Doing this avoids the mess we’d get into otherwise, for example:

(define (eval sexpr)
  (match sexpr
    [(number: n) n]
    [(list '+ left right) (+ (eval left) (eval right))]
    [(list '- left right) (- (eval left) (eval right))]
    [else (error 'eval "bad syntax in ~s" sexpr)]))

This is messy because it combines two very different things — syntax and semantics — into a single lump of code. For this particular kind of evaluator it looks simple enough, but this is only because it’s simple enough that all we do is replace constructors by arithmetic operations. Later on things will get more complex, and bundling the evaluator with the parser will be more problematic. (Note: the fact that we can replace constructors with the run-time operators mean that we have a very simple, calculator-like language, and that we can, in fact, “compile” all programs down to a number.)

If we split the code, we can easily include decisions like making

{+ 1 {- 3 "a"}}

syntactically invalid. (Which is not, BTW, what Racket does…) (Also, this is like the distinction between XML syntax and well-formed XML syntax.)

An additional advantage is that by using two separate components, it is simple to replace each one, making it possible to change the input syntax, and the semantics independently — we only need to keep the same interface data (the AST) and things will work fine.

Our parse function converts an input syntax to an abstract syntax tree (AST). It is abstract exactly because it is independent of any actual concrete syntax that you type in, print out etc.

Tuesday, January 14th Implementing The AE Language

Back to our eval — this will be its (obvious) type:

(: eval : AE -> Number)
;; consumes an AE and computes
;; the corresponding number

which leads to some obvious test cases:

(equal? 3 (eval (parse "3")))
(equal? 7 (eval (parse "{+ 3 4}")))
(equal? 6 (eval (parse "{+ {- 3 4} 7}")))

which from now on we will write using the new test form that the #lang pl language provides:

(test (eval (parse "3"))            => 3)
(test (eval (parse "{+ 3 4}"))      => 7)
(test (eval (parse "{+ {- 3 4} 7}")) => 6)

Note that we’re testing only at the interface level — only running whole functions. For example, you could think about a test like:

(test (parse "{+ {- 3 4} 7}")
      => (Add (Sub (Num 3) (Num 4)) (Num 7)))

but the details of parsing and of the constructor names are things that nobody outside of our evaluator cares about — so we’re not testing them. In fact, we shouldn’t even mention parse in these tests, since it is not part of the public interface of our users; they only care about using it as a compiler-like black box. (This is sometimes called “integration tests”.) We’ll address this shortly.

Like everything else, the structure of the recursive eval code follows the recursive structure of its input. In HtDP terms, our template is:

(: eval : AE -> Number)
(define (eval expr)
  (cases expr
    [(Num n)  ... n ...]
    [(Add l r) ... (eval l) ... (eval r) ...]
    [(Sub l r) ... (eval l) ... (eval r) ...]))

In this case, filling in the gaps is very simple

(: eval : AE -> Number)
(define (eval expr)
  (cases expr
    [(Num n)  n]
    [(Add l r) (+ (eval l) (eval r))]
    [(Sub l r) (- (eval l) (eval r))]))

We now further combine eval and parse into a single run function that evaluates an AE string.

(: run : String -> Number)
;; evaluate an AE program contained in a string
(define (run str)
  (eval (parse str)))

This function becomes the single public entry point into our code, and the only thing that should be used in tests that verify our interface:

(test (run "3")            => 3)
(test (run "{+ 3 4}")      => 7)
(test (run "{+ {- 3 4} 7}") => 6)

The resulting full code is:


ae.rkt D 
#lang pl

#| BNF for the AE language:
  <AE> ::= <num>
          | { + <AE> <AE> }
          | { - <AE> <AE> }
          | { * <AE> <AE> }
          | { / <AE> <AE> }
|#

;; AE abstract syntax trees
(define-type AE
  [Num Number]
  [Add AE AE]
  [Sub AE AE]
  [Mul AE AE]
  [Div AE AE])

(: parse-sexpr : Sexpr -> AE)
;; parses s-expressions into AEs
(define (parse-sexpr sexpr)
  (match sexpr
    [(number: n) (Num n)]
    [(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))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

(: parse : String -> AE)
;; parses a string containing an AE expression to an AE AST
(define (parse str)
  (parse-sexpr (string->sexpr str)))

(: eval : AE -> Number)
;; consumes an AE and computes the corresponding number
(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))]))

(: run : String -> Number)
;; evaluate an AE program contained in a string
(define (run str)
  (eval (parse str)))

;; tests
(test (run "3") => 3)
(test (run "{+ 3 4}") => 7)
(test (run "{+ {- 3 4} 7}") => 6)

(Note that the tests are done with a test form, which we mentioned above.)

For anyone who thinks that Racket is a bad choice, this is a good point to think how much code would be needed in some other language to do the same as above.

Tuesday, January 14th Intro to Typed Racket

The plan:

Types

Typed Racket will be both similar to and very different from anything you’ve seen before.

Why types?

Types will help you. A lot.

Structuring programs

Why Typed Racket?

Racket is the language we all know, and it has the benefits that we discussed earlier. Mainly, it is an excellent language for experimenting with programming languages.

[Also: the development of Typed Racket is happening here in Northeastern, and will benefit from your feedback.]

How is Typed Racket different from Racket

Examples

(: digit-num : Number -> (U Number String))
(define (digit-num n)
  (cond [(<= n 9)    1]
        [(<= n 99)  2]
        [(<= n 999)  3]
        [(<= n 9999) 4]
        [else        "a lot"]))

(: fact : Number -> Number)
(define (fact n)
  (if (zero? n)
    1
    (* n (fact (- n 1)))))

(: helper : Number Number -> Number)
(define (helper n acc)
  (if (zero? n)
    acc
    (helper (- n 1) (* acc n))))
(: fact : Number -> Number)
(define (fact n)
  (helper n 1))

(: fact : Number -> Number)
(define (fact n)
  (: helper : Number Number -> Number)
  (define (helper n acc)
    (if (zero? n)
      acc
      (helper (- n 1) (* acc n))))
  (helper n 1))

(: every? : (All (A) (A -> Boolean) (Listof A) -> Boolean))
;; Returns false if any element of lst fails the given pred,
;; true if all pass pred.
(define (every? pred lst)
  (or (null? lst)
      (and (pred (first lst))
          (every? pred (rest lst)))))

(define-type AE
  [Num Number]
  [Add AE AE]
  [Sub AE AE])

;; the only difference in the following definition is
;; using (: <name> : <type>) instead of ";; <name> : <type>"

(: parse-sexpr : Sexpr -> AE)
;; parses s-expressions into AEs
(define (parse-sexpr sexpr)
  (match sexpr
    [(number: n) (Num n)]
    [(list '+ left right)
    (Add (parse-sexpr left) (parse-sexpr right))]
    [(list '- left right)
    (Sub (parse-sexpr left) (parse-sexpr right))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

More interesting examples

Tuesday, January 14th Bindings & Substitution

We now get to an important concept: substitution.

Even in our simple language, we encounter repeated expressions. For example, if we want to compute the square of some expression:

{* {+ 4 2} {+ 4 2}}

Why would we want to get rid of the repeated sub-expression?

So, the normal way to avoid redundancy is to introduce an identifier. Even when we speak, we might say: “let x be 4 plus 2, multiply x by x”.

(These are often called “variables”, but we will try to avoid this name: what if the identifier does not change (vary)?)

To get this, we introduce a new form into our language:

{with {x {+ 4 2}}
  {* x x}}

We expect to be able to reduce this to:

{* 6 6}

by substituting 6 for x in the body sub-expression of with.

A little more complicated example:

{with {x {+ 4 2}}
  {with {y {* x x}}
    {+ y y}}}
[add]  = {with {x 6} {with {y {* x x}} {+ y y}}}
[subst]= {with {y {* 6 6}} {+ y y}}
[mul]  = {with {y 36} {+ y y}}
[subst]= {+ 36 36}
[add]  = 72

Tuesday, January 21st WAE: Adding Bindings to AE

PLAI §3

To add this to our language, we start with the BNF. We now call our language “WAE” (With+AE):

<WAE> ::= <num>
        | { + <WAE> <WAE> }
        | { - <WAE> <WAE> }
        | { * <WAE> <WAE> }
        | { / <WAE> <WAE> }
        | { with { <id> <WAE> } <WAE> }
        | <id>

Note that we had to introduce two new rules: one for introducing an identifier, and one for using it. This is common in many language specifications, for example define-type introduces a new type, and it comes with cases that allows us to destruct its instances.

For <id> we need to use some form of identifiers, the natural choice in Racket is to use symbols. We can therefore write the corresponding type definition:

(define-type WAE
  [Num  Number]
  [Add  WAE WAE]
  [Sub  WAE WAE]
  [Mul  WAE WAE]
  [Div  WAE WAE]
  [Id  Symbol]
  [With Symbol WAE WAE])

The parser is easily extended to produce these syntax objects:

(: parse-sexpr : Sexpr -> WAE)
;; parses s-expressions into WAEs
(define (parse-sexpr sexpr)
  (match sexpr
    [(number: n)    (Num n)]
    [(symbol: name) (Id name)]
    [(list 'with (list (symbol: name) named) body)
    (With name (parse-sexpr named) (parse-sexpr body))]
    [(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))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

But note that this parser is inconvenient — if any of these expressions:

{* 1 2 3}
{foo 5 6}
{with x 5 {* x 8}}
{with {5 x} {* x 8}}

would result in a “bad syntax” error, which is not very helpful. To make things better, we can add another case for with expressions that are malformed, and give a more specific message in that case:

(: parse-sexpr : Sexpr -> WAE)
;; parses s-expressions into WAEs
(define (parse-sexpr sexpr)
  (match sexpr
    [(number: n)    (Num n)]
    [(symbol: name) (Id name)]
    [(list 'with (list (symbol: name) named) body)
    (With name (parse-sexpr named) (parse-sexpr body))]
    [(cons 'with more)
    (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) (Div (parse-sexpr lhs) (parse-sexpr rhs))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

and finally, to group all of the parsing code that deals with with expressions (both valid and invalid ones), we can use a single case for both of them:

(: parse-sexpr : Sexpr -> WAE)
;; parses s-expressions into WAEs
(define (parse-sexpr sexpr)
  (match sexpr
    [(number: n)    (Num n)]
    [(symbol: name) (Id name)]
    [(cons 'with more)
    ;; go in here for all sexpr that begin with a 'with
    (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) (Div (parse-sexpr lhs) (parse-sexpr rhs))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

And now we’re done with the syntactic part of the with extension.

Quick note — why would we indent With like a normal function in code like this

(With 'x
      (Num 2)
      (Add (Id 'x) (Num 4)))

instead of an indentation that looks like a let

(With 'x (Num 2)
  (Add (Id 'x) (Num 4)))

?

The reason for this is that the second indentation looks like a binding construct (eg, the indentation used in a let expression), but With is not a binding form — it’s a plain function because it’s at the Racket level. You should therefore keep in mind the huge difference between that With and the with that appears in WAE programs:

{with {x 2}
  {+ x 4}}

Another way to look at it: imagine that we intend for the language to be used by Spanish/Chinese/German/French speakers. In this case we would translate “with”:

{con  {x 2} {+ x 4}}
{he  {x 2} {+ x 4}}
{mit  {x 2} {+ x 4}}
{avec {x 2} {+ x 4}}
{c    {x 2} {+ x 4}}

but we will not do the same for With if we (the language implementors) are English speakers.

Tuesday, January 21st Evaluation of with

Now, to make this work, we will need to do some substitutions.

We basically want to say that to evaluate:

{with {id WAE1} WAE2}

we need to evaluate WAE2 with id substituted by WAE1. Formally:

eval( {with {id WAE1} WAE2} )
  = eval( subst(WAE2,id,WAE1) )

There is a more common syntax for substitution (quick: what do I mean by this use of “syntax”?):

eval( {with {id WAE1} WAE2} )
  = eval( WAE2[WAE1/id] )

Side-note: this syntax originates with logicians who used [x/v]e, and later there was a convention that mimicked the more natural order of arguments to a function with e[x->v], and eventually both of these got combined into e[v/x] which is a little confusing in that the left-to-right order of the arguments is not the same as for the subst function.

Now all we need is an exact definition of substitution.

Note that substitution is not the same as evaluation, it’s only a part of the evaluation process. In the previous examples, when we evaluated the expression we did substitutions as well as the usual arithmetic operations that were already part of the AE evaluator. In this last definition there is still a missing evaluation step, see if you can find it.

So let us try to define substitution now:

Substitution (take 1): e[v/i]
To substitute an identifier i in an expression e with an expression v, replace all identifiers in e that have the same name i by the expression v.

This seems to work with simple expressions, for example:

{with {x 5} {+ x x}}  -->  {+ 5 5}
{with {x 5} {+ 10 4}}  -->  {+ 10 4}

however, we crash with an invalid syntax if we try:

{with {x 5} {+ x {with {x 3} 10}}}
  -->  {+ 5 {with {5 3} 10}} ???

— we got to an invalid expression.

To fix this, we need to distinguish normal occurrences of identifiers, and ones that are used as new bindings. We need a few new terms for this:

  1. Binding Instance: a binding instance of an identifier is one that is used to name it in a new binding. In our <WAE> syntax, binding instances are only the <id> position of the with form.

  2. Scope: the scope of a binding instance is the region of program text in which instances of the identifier refer to the value bound in the binding instance. (Note that this definition actually relies on a definition of substitution, because that is what is used to specify how identifiers refer to values.)

  3. Bound Instance (or Bound Occurrence / Identifier): an instance of an identifier is bound if it is contained within the scope of a binding instance of its name.

  4. Free Instance (or Free Occurrence / Identifier): An identifier that is not contained in any binding instance of its name is said to be free.

Using this we can say that the problem with the previous definition of substitution is that it failed to distinguish between bound instances (which should be substituted) and binding instances (which should not). So we try to fix this:

Substitution (take 2): e[v/i]
To substitute an identifier i in an expression e with an expression v, replace all instances of i that are not themselves binding instances with the expression v.

First of all, check the previous examples:

{with {x 5} {+ x x}}  -->  {+ 5 5}
{with {x 5} {+ 10 4}}  -->  {+ 10 4}

still work, and

{with {x 5} {+ x {with {x 3} 10}}}
  -->  {+ 5 {with {x 3} 10}}
  -->  {+ 5 10}

also works. However, if we try this:

{with {x 5}
  {+ x {with {x 3}
        x}}}

we get:

-->  {+ 5 {with {x 3} 5}}
-->  {+ 5 5}
-->  10

but we want that to be 8: the inner x should be bound by the closest with that binds it.

The problem is that the new definition of substitution that we have respects binding instances, but it fails to deal with their scope. In the above example, we want the inner with to shadow the outer with’s binding for x.

Substitution (take 3): e[v/i]
To substitute an identifier i in an expression e with an expression v, replace all instances of i that are not themselves binding instances, and that are not in any nested scope, with the expression v.

This avoids bad substitution above, but it is now doing things too carefully:

{with {x 5} {+ x {with {y 3} x}}}

becomes

-->  {+ 5 {with {y 3} x}}
-->  {+ 5 x}

which is an error because x is unbound (and there is reasonable no rule that we can specify to evaluate it).

The problem is that our substitution halts at every new scope, in this case, it stopped at the new y scope, but it shouldn’t have because it uses a different name. In fact, that last definition of substitution cannot handle any nested scope.

Revise again:

Substitution (take 4): e[v/i]
To substitute an identifier i in an expression e with an expression v, replace all instances of i that are not themselves binding instances, and that are not in any nested scope of i, with the expression v.

which, finally, is a good definition. This is just a little too mechanical. Notice that we actually refer to all instances of i that are not in a scope of a binding instance of i, which simply means all free occurrences of i — free in e (why? — remember the definition of “free”?):

Substitution (take 4b): 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.

Based on this we can finally write the code for it:

(: 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) ; returns expr[to/from]
  (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)
    (if (eq? bound-id from)
      expr          ;*** don't go in!
      (With bound-id
            named-expr
            (subst bound-body from to)))]))

… and this is just the same as writing a formal “paper version” of the substitution rule.

We still have bugs: but we’ll need some more work to get to them.


Before we find the bugs, we need to see when and how substitution is used in the evaluation process.

To modify our evaluator, we will need rules to deal with the new syntax pieces — with expressions and identifiers.

When we see an expression that looks like:

{with {x E1} E2}

we continue by evaluating E1 to get a value V1, we then substitute the identifier x with the expression V1 in E2, and continue by evaluating this new expression. In other words, we have the following evaluation rule:

eval( {with {x E1} E2} )
  = eval( E2[eval(E1)/x] )

So we know what to do with with expressions. How about identifiers? The main feature of subst, as said in the purpose statement, is that it leaves no free instances of the substituted variable around. This means that if the initial expression is valid (did not contain any free variables), then when we go from

{with {x E1} E2}

to

E2[E1/x]

the result is an expression that has no free instances of x. So we don’t need to handle identifiers in the evaluator — substitutions make them all go away.

We can now extend the formal definition of AE to that of WAE:

eval(...) = ... same as the AE rules ...
eval({with {x E1} E2}) = eval(E2[eval(E1)/x])
eval(id) = error!

If you’re paying close attention, you might catch a potential problem in this definition: we’re substituting eval(E1) for x in E2 — an operation that requires a WAE expression, but eval(E1) is a number. (Look at the type of the eval definition we had for AE, then look at the above definition of subst.) This seems like being overly pedantic, but we it will require some resolution when we get to the code. The above rules are easily coded as follows:

(: 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)]))

Note the Num expression in the marked line: evaluating the named expression gives us back a number — we need to convert this number into a syntax to be able to use it with subst. The solution is to use Num to convert the resulting number into a numeral (the syntax of a number). It’s not an elegant solution, but it will do for now.

Finally, here are a few test cases. We use a new test special form which is part of the course plugin. The way to use test is with two expressions and an => arrow — DrRacket evaluates both, and nothing will happen if the results are equal. If the results are different, you will get a warning line, but evaluation will continue so you can try additional tests. You can also use an =error> arrow to test an error message — use it with some text from the expected error, ? stands for any single character, and * is a sequence of zero or more characters. (When you use test in your homework, the handin server will abort when tests fail.) We expect these tests to succeed (make sure that you understand why they should succeed).

;; tests
(test (run "5") => 5)
(test (run "{+ 5 5}") => 10)
(test (run "{with {x {+ 5 5}} {+ x x}}") => 20)
(test (run "{with {x 5} {+ x x}}") => 10)
(test (run "{with {x {+ 5 5}} {with {y {- x 3}} {+ y y}}}") => 14)
(test (run "{with {x 5} {with {y {- x 3}} {+ y y}}}") => 4)
(test (run "{with {x 5} {+ x {with {x 3} 10}}}") => 15)
(test (run "{with {x 5} {+ x {with {x 3} x}}}") => 8)
(test (run "{with {x 5} {+ x {with {y 3} x}}}") => 10)
(test (run "{with {x 5} {with {y x} y}}") => 5)
(test (run "{with {x 5} {with {x x} x}}") => 5)
(test (run "{with {x 1} y}") =error> "free identifier")

Putting this all together, we get the following code; trying to run this code will raise an unexpected error…

#lang pl

#| BNF for the WAE language:
    <WAE> ::= <num>
            | { + <WAE> <WAE> }
            | { - <WAE> <WAE> }
            | { * <WAE> <WAE> }
            | { / <WAE> <WAE> }
            | { with { <id> <WAE> } <WAE> }
            | <id>
|#

;; WAE abstract syntax trees
(define-type WAE
  [Num  Number]
  [Add  WAE WAE]
  [Sub  WAE WAE]
  [Mul  WAE WAE]
  [Div  WAE WAE]
  [Id  Symbol]
  [With Symbol WAE WAE])

(: parse-sexpr : Sexpr -> WAE)
;; parses 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) (Div (parse-sexpr lhs) (parse-sexpr rhs))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

(: parse : String -> WAE)
;; parses a string containing a WAE expression to a WAE AST
(define (parse str)
  (parse-sexpr (string->sexpr str)))

(: 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)
    (if (eq? bound-id from)
      expr
      (With bound-id
            named-expr
            (subst bound-body from to)))]))

(: 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)]))

(: run : String -> Number)
;; evaluate a WAE program contained in a string
(define (run str)
  (eval (parse str)))

;; tests
(test (run "5") => 5)
(test (run "{+ 5 5}") => 10)
(test (run "{with {x {+ 5 5}} {+ x x}}") => 20)
(test (run "{with {x 5} {+ x x}}") => 10)
(test (run "{with {x {+ 5 5}} {with {y {- x 3}} {+ y y}}}") => 14)
(test (run "{with {x 5} {with {y {- x 3}} {+ y y}}}") => 4)
(test (run "{with {x 5} {+ x {with {x 3} 10}}}") => 15)
(test (run "{with {x 5} {+ x {with {x 3} x}}}") => 8)
(test (run "{with {x 5} {+ x {with {y 3} x}}}") => 10)
(test (run "{with {x 5} {with {y x} y}}") => 5)
(test (run "{with {x 5} {with {x x} x}}") => 5)
(test (run "{with {x 1} y}") =error> "free identifier")

Oops, this program still has problems that were caught by the tests — we encounter unexpected free identifier errors. What’s the problem now? In expressions like:

{with {x 5}
  {with {y x}
    y}}

we forgot to substitute x in the expression that y is bound to. We need to the recursive substitute in both the with’s body expression as well as its named expression:

(: 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)
    (if (eq? bound-id from)
      expr
      (With bound-id
            (subst named-expr from to)      ;*** new
            (subst bound-body from to)))]))

And still we have a problem… Now it’s

{with {x 5}
  {with {x x}
    x}}

that halts with an error, but we want it to evaluate to 5! Carefully trying out our substitution code reveals the problem: when we substitute 5 for the outer x, we don’t go inside the inner with because it has the same name — but we do need to go into its named expression. We need to substitute in the named expression even if the identifier is the same one we’re substituting:

(: 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)))]))

The complete (and, finally, correct) version of the code is now:


wae.rkt D 
#lang pl

#| BNF for the WAE language:
    <WAE> ::= <num>
            | { + <WAE> <WAE> }
            | { - <WAE> <WAE> }
            | { * <WAE> <WAE> }
            | { / <WAE> <WAE> }
            | { with { <id> <WAE> } <WAE> }
            | <id>
|#

;; WAE abstract syntax trees
(define-type WAE
  [Num  Number]
  [Add  WAE WAE]
  [Sub  WAE WAE]
  [Mul  WAE WAE]
  [Div  WAE WAE]
  [Id  Symbol]
  [With Symbol WAE WAE])

(: parse-sexpr : Sexpr -> WAE)
;; parses 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) (Div (parse-sexpr lhs) (parse-sexpr rhs))]
    [else (error 'parse-sexpr "bad syntax in ~s" sexpr)]))

(: parse : String -> WAE)
;; parses a string containing a WAE expression to a WAE AST
(define (parse str)
  (parse-sexpr (string->sexpr str)))

#| Formal specs for `subst':
  (`N' is a <num>, `E1', `E2' are <WAE>s, `x' is some <id>,
  `y' is a *different* <id>)
      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}
|#

(: 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)))]))

#| Formal specs for `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])
|#

(: 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)]))

(: run : String -> Number)
;; evaluate a WAE program contained in a string
(define (run str)
  (eval (parse str)))

;; tests
(test (run "5") => 5)
(test (run "{+ 5 5}") => 10)
(test (run "{with {x 5} {+ x x}}") => 10)
(test (run "{with {x {+ 5 5}} {+ x x}}") => 20)
(test (run "{with {x 5} {with {y {- x 3}} {+ y y}}}") => 4)
(test (run "{with {x {+ 5 5}} {with {y {- x 3}} {+ y y}}}") => 14)
(test (run "{with {x 5} {+ x {with {x 3} 10}}}") => 15)
(test (run "{with {x 5} {+ x {with {x 3} x}}}") => 8)
(test (run "{with {x 5} {+ x {with {y 3} x}}}") => 10)
(test (run "{with {x 5} {with {y x} y}}") => 5)
(test (run "{with {x 5} {with {x x} x}}") => 5)
(test (run "{with {x 1} y}") =error> "free identifier")

Tuesday, January 21st Evaluation of with (contd.)

Reminder:

Tuesday, January 21st 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 <num>, E1, E2 are <WAE>s, x is some <id>, y is a different <id>)

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])

Tuesday, January 21st 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) ; (put this in the definitions window)
> (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]} ; `y1' is "fresh"
  otherwise
    = {with {y E1[v/x]} E2[v/x]}

With this, we might have gone through this path in evaluating the above:

{with {y x} {with {x 2} {+ x y}}}
{with {x₁ 2} {+ x₁ x}} ; note that x₁ is a fresh name, not x
{+ 2 x}
error: free `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…)

Tuesday, January 21st de Bruijn Indexes

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}}

or the two Racket 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 DrRacket 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 Indexes”: 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. After all, abstractions are the main thing we deal with when we write programs, and having labels make the bindings structure much easier to understand than scope counts.

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)  ; int x = 5
movl -4(%ebp), %eax
incl %eax
movl %eax, -8(%ebp) ; int y = %eax
movl -8(%ebp), %eax
addl -4(%ebp), %eax

Tuesday, January 28th Functions & Function Values

PLAI §4

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 has all of the necessary ingredients of a function — a bunch of code that is parameterized over some input identifier:

{with {x}
  {* x x}}

We only need to replace with and use a proper name that indicates that it’s a function:

{fun {x}
  {* x x}}

Now we have a new form in our language, one that should have a function as its meaning. As we have seen in the case of with expressions, we also need a new form to use these functions. We will use call for this, so that

{call {fun {x} {* x x}}
      5}

will be the same as the original with expression that we started with — the fun expression is like the with expression with no value, and applying it on 5 is providing that value back:

{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). Note that naming functions often helps, but many times there are small functions that are fine to specify without a name — for example, consider a two-stage addition function, where there is no apparent good name for the returned function:

{with {add {fun {x}
            {fun {y}
              {+ x y}}}}
  {call {call add 8} 9}}

Tuesday, January 28th Implementing First Class Functions

PLAI §6 (uses some stuff from PLAI §5, which we do later)

This is a simple plan, but it is directly related to how functions are going to be used in our language. We know that {call {fun {x} E1} E2} is equivalent to a with expression, but the new thing here is that we do allow writing just the {fun ...} expression by itself, and therefore we need to have some meaning for it. The meaning, or the value of this expression, should roughly be “an expression that needs a value to be plugged in for x”. In other words, our language will have these new kinds of values that contain an expression to be evaluated later on.

There are three basic approaches that classify programming languages in relation to how the deal with functions:

  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 structures. This is what most “conventional” languages used to have in the past. (You will be implementing such a language in homework 4.)

    An example of such a language is the Beginner Student language that is used in HtDP, where the language is intentionally first-order to help students write correct code (at the early stages where using a function as a value is usually an error). It’s hard to find practical modern languages that fall in this category.

  2. Higher order: functions can receive and return other functions as values. This is what you get with C and modern Fortran.

  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. (And most modern languages have first class functions.)

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. The important thing is that the value exists independently of a name.

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 languages with just higher-order or first-order functions.


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);
  return [f(2), g(2)];
}

Note that the above definition of foo does not use an anonymous “lambda expression” — in Racket terms, it’s translated to

(define (foo x)
  (define (bar y) (+ x y))
  bar)

The returned function is not anonymous, but it’s not really named either: the bar name is bound only inside the body of foo, and outside of it that name no longer exists since it’s not its scope. It gets used in the printed form if the function value is displayed, but this is merely a debugging aid. The anonymous lambda version that is common in Racket can be used in JavaScript too:

function foo(x) {
  return function(y) { return x + y; }
}

Side-note: 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 <stdio.h>
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));
}

Tuesday, January 28th Side-note: how important is it to have anonymous functions?

You’ll see many places where people refer to the feature of first-class functions as the ability to create anonymous functions, but this is a confusion and it’s not accurate. Whether a function has a name or not is not the important question — instead, the important question is whether functions can exist with no bindings that refers to them.

As a quick example in Racket:

(define (foo x)
  (define (bar y) (+ x y))
  bar)

in Javascript:

function foo(x) {
  function bar(y) {
    return x + y;
  }
  return bar;
}

and in Python:

def foo(x):
  def bar(y):
    return x + y
  return bar

In all three of these, we have a foo function that returns a function named bar — but the bar name, is only available in the scope of foo. The fact that the name is displayed as part of the textual rendering of the function value is merely a debugging feature.

Tuesday, January 28th The FLANG Language

Now for the implementation — we call this new language FLANG.

First, the BNF:

<FLANG> ::= <num>
          | { + <FLANG> <FLANG> }
          | { - <FLANG> <FLANG> }
          | { * <FLANG> <FLANG> }
          | { / <FLANG> <FLANG> }
          | { with { <id> <FLANG> } <FLANG> }
          | <id>
          | { fun { <id> } <FLANG> }
          | { call <FLANG> <FLANG> }

And the matching type definition:

(define-type FLANG
  [Num  Number]
  [Add  FLANG FLANG]
  [Sub  FLANG FLANG]
  [Mul  FLANG FLANG]
  [Div  FLANG FLANG]
  [Id  Symbol]
  [With Symbol FLANG FLANG]
  [Fun  Symbol      FLANG] ; No named-expression
  [Call FLANG FLANG])

The parser for this grammar is, as usual, straightforward:

(: parse-sexpr : Sexpr -> FLANG)
;; parses 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 number values and we used Racket numbers to represent them. Now we have two kinds of values — numbers and functions. It seems easy enough to continue using Racket 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 should be a function value, which is something that can be used just like numbers, but instead of arithmetic operations, we can call these things. What we need is a way to avoid evaluating the body expression of the function — delay it — and instead use some value that will contain this delayed expression in a way that can be used later.

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. The syntax object has what we need: the body expression that needs to be evaluated later when the function is called, and it also has the identifier name that should be replaced with the actual input to the function call. 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(B[eval(E2)/x])  if eval(E1) = {fun {x} B}
                = 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} B}) if eval(E1) = {fun {x} B}
                = 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 which is intuitively seen by the fact that the evaluation rule for a call is expected to be very similar to the one for arithmetic operations. We now have two kinds of values, so we need to check the arithmetic operation’s arguments too:

eval({+ E1 E2}) = N1 + N2
                    if eval(E1), eval(E2) evaluate to numbers N1, N2
                    otherwise error!
...

The corresponding code is:

(: eval : FLANG -> FLANG)                      ;*** note return type
;; evaluates FLANG expressions by reducing them to *expressions* but
;; only expressions that stand for values: only `Fun`s and `Num`s
(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)]))

Note that the Call case is doing the same thing we do in the With case. In fact, we could have just generated a With expression and evaluate that instead:

    ...
    [(Call (Fun bound-id bound-body) arg-expr)
    (eval (With bound-id arg-expr bound-body))]
    ...

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 Racket 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 Racket numeric binary operator, and uses it within a FLANG
;; `Num' wrapper (note the H.O. type, and note the hack of the `val`
;; name which is actually an AST that represents a runtime value)
(define (arith-op op val1 val2)
  (Num (op (Num->number val1) (Num->number val2))))

It uses the following function to convert FLANG numbers to Racket numbers. (Note that else is almost always a bad idea since it can prevent the compiler from showing you places to edit code — but this case is an exception since we never want to deal with anything other than Nums.) The reason that this function is relatively trivial is that we chose the easy way and represented numbers using Racket numbers, but we could have used strings or anything else.

(: Num->number : FLANG -> Number)
;; convert a FLANG number to a Racket one
(define (Num->number e)
  (cases e
    [(Num n) n]
    [else (error 'arith-op "expected a number, got: ~s" e)]))

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:
  <FLANG> ::= <num>
            | { + <FLANG> <FLANG> }
            | { - <FLANG> <FLANG> }
            | { * <FLANG> <FLANG> }
            | { / <FLANG> <FLANG> }
            | { with { <id> <FLANG> } <FLANG> }
            | <id>
            | { fun { <id> } <FLANG> }
            | { call <FLANG> <FLANG> }

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(B[eval(E2)/x])
                          if eval(E1)={fun {x} B}, otherwise error!
|#

(define-type FLANG
  [Num  Number]
  [Add  FLANG FLANG]
  [Sub  FLANG FLANG]
  [Mul  FLANG FLANG]
  [Div  FLANG FLANG]
  [Id  Symbol]
  [With Symbol FLANG FLANG]
  [Fun  Symbol FLANG]
  [Call FLANG FLANG])

(: parse-sexpr : Sexpr -> FLANG)
;; parses 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)))]))

(: Num->number : FLANG -> Number)
;; convert a FLANG number to a Racket one
(define (Num->number e)
  (cases e
    [(Num n) n]
    [else (error 'arith-op "expected a number, got: ~s" e)]))

(: arith-op : (Number Number -> Number) FLANG FLANG -> FLANG)
;; gets a Racket numeric binary operator, and uses it within a FLANG
;; `Num' wrapper
(define (arith-op op val1 val2)
  (Num (op (Num->number val1) (Num->number val2))))

(: eval : FLANG -> FLANG)
;; evaluates FLANG expressions by reducing them to *expressions* but
;; only expressions that stand for values: only `Fun`s and `Num`s
(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)

Tuesday, January 28th The FLANG Language (contd.)

There is still a problem with this version. First a question — if call is similar to arithmetic operations (and to with in what it actually does), then how come the code is different enough that it doesn’t even need an auxiliary function?

Second question: what should happen if we evaluate these code snippets:

(run "{with {add {fun {x}
                  {fun {y}
                    {+ x y}}}}
      {call {call add 8} 9}}")
(run "{with {identity {fun {x} x}}
        {with {foo {fun {x} {+ x 1}}}
          {call {call identity foo} 123}}}")
(run "{call {call {fun {x} {call x 1}}
                  {fun {x} {fun {y} {+ x y}}}}
            123}")

Third question, what will happen if we do the above?

What we’re missing is an evaluation of the function expression, in case it’s not a literal fun form. The following fixes this:

(: eval : FLANG -> FLANG)
;; evaluates FLANG expressions by reducing them to *expressions* but
;; only expressions that stand for values: only `Fun`s and `Num`s
(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-expr arg-expr)
    (define fval (eval fun-expr))      ;*** need to evaluate this!
    (cases fval
      [(Fun bound-id bound-body)
        (eval (subst bound-body bound-id (eval arg-expr)))]
      [else (error 'eval "`call' expects a function, got: ~s"
                    fval)])]))

The complete code is:


flang.rkt D 
;; The Flang interpreter

#lang pl

#|
The grammar:
  <FLANG> ::= <num>
            | { + <FLANG> <FLANG> }
            | { - <FLANG> <FLANG> }
            | { * <FLANG> <FLANG> }
            | { / <FLANG> <FLANG> }
            | { with { <id> <FLANG> } <FLANG> }
            | <id>
            | { fun { <id> } <FLANG> }
            | { call <FLANG> <FLANG> }

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(B[eval(E2)/x])
                          if eval(E1)={fun {x} B}, otherwise error!
|#

(define-type FLANG
  [Num  Number]
  [Add  FLANG FLANG]
  [Sub  FLANG FLANG]
  [Mul  FLANG FLANG]
  [Div  FLANG FLANG]
  [Id  Symbol]
  [With Symbol FLANG FLANG]
  [Fun  Symbol FLANG]
  [Call FLANG FLANG])

(: parse-sexpr : Sexpr -> FLANG)
;; parses 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)))]))

(: Num->number : FLANG -> Number)
;; convert a FLANG number to a Racket one
(define (Num->number e)
  (cases e
    [(Num n) n]
    [else (error 'arith-op "expected a number, got: ~s" e)]))

(: arith-op : (Number Number -> Number) FLANG FLANG -> FLANG)
;; gets a Racket numeric binary operator, and uses it within a FLANG
;; `Num' wrapper
(define (arith-op op val1 val2)
  (Num (op (Num->number val1) (Num->number val2))))

(: eval : FLANG -> FLANG)
;; evaluates FLANG expressions by reducing them to *expressions* but
;; only expressions that stand for values: only `Fun`s and `Num`s
(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-expr arg-expr)
    (define fval (eval fun-expr))
    (cases fval
      [(Fun bound-id bound-body)
        (eval (subst bound-body bound-id (eval arg-expr)))]
      [else (error 'eval "`call' expects a function, got: ~s"
                    fval)])]))

(: 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)
(test (run "{with {add {fun {x}
                        {fun {y}
                          {+ x y}}}}
              {call {call add 8} 9}}")
      => 17)
(test (run "{with {identity {fun {x} x}}
              {with {foo {fun {x} {+ x 1}}}
                {call {call identity foo} 123}}}")
      => 124)
(test (run "{call {call {fun {x} {call x 1}}
                        {fun {x} {fun {y} {+ x y}}}}
                  123}")
      => 124)

Tuesday, January 28th Introducing Racket’s lambda

Quick laundry list of things to go over:

  • fun & lambda
  • difference between lambda and simple values
  • not being able to do recursive functions with let
  • let* as a derived form
  • let with lambda in Racket –> can be a derived form
  • how if can be used to implement and and or as derived forms
  • Newtonian syntax vs. a lambda expression.

Almost all modern languages have this capability. For example, this:

(define (f g) (g 2 3))
(f +) ==> 5
(f *) ==> 6
(f (lambda (x y) (+ (square x) (square y)))) ==> 13

Can be written in JavaScript like this:

function f(g) { return g(2,3); }
function square(x) { return x*x; }
console.log(f(function (x,y) { return square(x) + square(y); }));

or in ES6 JavaScript:

let f = (g) => g(2,3);
let square = (x) => x*x;
console.log(f((x,y) => square(x) + square(y)));

In Perl:

sub f { my ($g) = @_; return $g->(2,3); }
sub square { my ($x) = @_; return $x * $x; }
print f(sub { my ($x, $y) = @_; return square($x) + square($y); });

In Ruby:

def f(g) g.call(2,3) end
def square(x) x*x end
puts f(lambda{|x,y| square(x) + square(y)})

etc. Even Java has lambda expressions, and “recently” C++ added them too.

Tuesday, January 28th Using Functions as Objects

A very important aspect of Racket — using “higher order” functions — functions that get and return functions. Here is a very simple example:

(define (f x) (lambda () x))
(define a (f 2))
(a)  -->  2
(define b (f 3))
(b)  -->  3

Note: what we get is actually an object that remembers (by the substitution we’re doing) a number. How about:

(define aa (f a))
(aa)    -->  #<procedure> (this is a)
((aa))  -->  2

Take this idea to the next level:

(define (kons x y)
  (lambda (b)
    (if b x y)))
(define (kar p) (p #t))
(define (kdr p) (p #f))
(define a (kons 1 2))
(define b (kons 3 4))
(list (kar a) (kdr a))
(list (kar b) (kdr b))

Or, with types:

(: kons : (All (A B) A B -> (Boolean -> (U A B))))
(define (kons x y)
  (lambda (b)
    (if b x y)))
(: kar : (All (T) (Boolean -> T) -> T))
(define (kar p) (p #t))
(: kdr : (All (T) (Boolean -> T) -> T))
(define (kdr p) (p #f))
(define a (kons 1 2))
(define b (kons 3 4))
(list (kar a) (kdr a))
(list (kar b) (kdr b))

Even more — why should the internal function expect a boolean and choose what to return? We can simply expect a function that will take the two values and return one:

(define (kons x y) (lambda (s) (s x y)))
(define (kar p) (p (lambda (x y) x)))
(define (kdr p) (p (lambda (x y) y)))
(define a (kons 1 2))
(define b (kons 3 4))
(list (kar a) (kdr a))
(list (kar b) (kdr b))

And a typed version, using our own constructor to make it a little less painful:

(define-type (Kons A B) = ((A B -> (U A B)) -> (U A B)))

(: kons : (All (A B) A B -> (Kons A B)))
(define (kons x y) (lambda (s) (s x y)))
(: kar : (All (A B) (Kons A B) -> (U A B)))
(define (kar p) (p (lambda (x y) x)))
(: kdr : (All (A B) (Kons A B) -> (U A B)))
(define (kdr p) (p (lambda (x y) y)))
(define a (kons 1 2))
(define b (kons 3 4))
(list (kar a) (kdr a))
(list (kar b) (kdr b))

Note that the Kons type definition is the same as:

(define-type Kons = (All (A B) (A B -> (U A B)) -> (U A B)))

so All is to polymorphic type definitions what lambda is for function definitions.

Finally, in JavaScript:

function kons(x,y) { return function(s) { return s(x, y); } }
function kar(p) { return p(function(x,y){ return x; }); }
function kdr(p) { return p(function(x,y){ return y; }); }
a = kons(1,2);
b = kons(3,4);
console.log('a = <' + kar(a) + ',' + kdr(a) + '>' );
console.log('b = <' + kar(b) + ',' + kdr(b) + '>' );

or with ES6 arrow functions, the function definitions become:

const kons = (x,y) => s => s(x,y);
const kar  = p => p((x,y) => x);
const kdr  = p => p((x,y) => y);

and using Typescript to add types:

type Kons<A,B> = (s: (x:A, y:B) => A|B) => A | B
const kons = <A,B>(x:A,y:B) => (s: ((x:A, y:B) => A|B)) => s(x,y);
const kar  = <A,B>(p: Kons<A,B>) => p((x,y) => x);
const kdr  = <A,B>(p: Kons<A,B>) => p((x,y) => y);

Tuesday, January 28th Using define-type for new “type aliases”

As seen in these examples, there is another way to use define-type, using a = to create a new type name “alias” for an existing type. For example:

(define-type Strings = (Listof String))

These uses of define-type do not define any new kind of type, they are essentially a convenience tool for making code shorter and more readable.

(define-type NumericFunction = Number -> Number)

(: square : NumericFunction)
(define (square n) (* n n))

Note in particular that this can also be used to define “alias type constructors” too: somewhat similar to creating new “type functions”. For example:

(define-type (BinaryFun In Out) = In In -> Out)

(: diagonal : (BinaryFun Natural Number))
(define (diagonal width height)
  (sqrt (+ (* width width) (* height height))))

This is something that we will only need in a few rare cases.

Tuesday, January 28th Currying

A curried function is a function that, instead of accepting two (or more) arguments, accepts only one and returns a function that accepts the rest. For example:

(: plus : Number -> (Number -> Number))
(define (plus x)
  (lambda (y)
    (+ x y)))

It’s easy to write functions for translating between normal and curried versions.

(define (currify f)
  (lambda (x)
    (lambda (y)
      (f x y))))

Typed version of that, with examples:

(: currify : (All (A B C) (A B -> C) -> (A -> (B -> C))))
;; convert a double-argument function to a curried one
(define (currify f)
  (lambda (x) (lambda (y) (f x y))))

(: add : Number Number -> Number)
(define (add x y) (+ x y))

(: plus : Number -> (Number -> Number))
(define plus (currify add))

(test ((plus 1) 2) => 3)
(test (((currify add) 1) 2) => 3)
(test (map (plus 1) '(1 2 3)) => '(2 3 4))
(test (map ((currify add) 1) '(1 2 3)) => '(2 3 4))
(test (map ((currify +) 1) '(1 2 3)) => '(2 3 4))

Usages — common with H.O. functions like map, where we want to fix one argument.

When dealing with such higher-order code, the types are very helpful, since every arrow corresponds to a function:

(: currify : (All (A B C) (A B -> C) -> (A -> (B -> C))))

It is common to make the -> function type associate to the right, so you can find this type written as:

currify : (A B -> C) -> (A -> B -> C)

or even as

currify : (A B -> C) -> A -> B -> C

but that can be a little confusing…

Tuesday, January 28th Using Higher-Order & Anonymous Functions

Say that we have a function for estimating derivatives of a function at a specific point:

(define dx 0.01)

(: deriv : (Number -> Number) Number -> Number)
;; compute the derivative of `f' at the given point `x'
(define (deriv f x)
  (/ (- (f (+ x dx)) (f x)) dx))

(: integrate : (Number -> Number) Number -> Number)
;; compute an integral of `f' at the given point `x'
(define (integrate f x)
  (: loop : Number Number -> Number)
  (define (loop y acc)
    (if (> y x)
      (* acc dx)
      (loop (+ y dx) (+ acc (f y)))))
  (loop 0 0))

And say that we want to try out various functions given some plot function that draws graphs of numeric functions, for example:

(plot sin)

The problem is that plot expects a single (Number -> Number) function — if we want to try it with a derivative, we can do this:

(: sin-deriv : Number -> Number)
;; the derivative of sin
(define sin-deriv (lambda (x) (deriv sin x)))
(plot sin-deriv)

But this will get very tedious very fast — it is much simpler to use an anonymous function:

(plot (lambda (x) (deriv sin x)))

we can even verify that our derivative is correct by comparing a known function to its derivative

(plot (lambda (x) (- (deriv sin x) (cos x))))

But it’s still not completely natural to do these things — you need to explicitly combine functions, which is not too convenient. Instead of doing this, we can write H.O. functions that will work with functional inputs and outputs. For example, we can write a function to subtract functions:

(: fsub : (Number -> Number) (Number -> Number)
          -> (Number -> Number))
;; subtracts two numeric 1-argument functions
(define (fsub f g)
  (lambda (x) (- (f x) (g x))))

and the same for the derivative:

(: fderiv : (Number -> Number) -> (Number -> Number))
;; compute the derivative function of `f'
(define (fderiv f)
  (lambda (x) (deriv f x)))

Now we can try the same in a much easier way:

(plot (fsub (fderiv sin) cos))

More than that — our fderiv could be created from deriv automatically:

(: currify : (All (A B C) (A B -> C) -> (A -> B -> C)))
;; convert a double-argument function to a curried one
(define (currify f)
  (lambda (x) (lambda (y) (f x y))))

(: fderiv : (Number -> Number) -> (Number -> Number))
;; compute the derivative function of `f'
(define fderiv (currify deriv))

Same principle with fsub: we can write a function that converts a binary arithmetical function into a function that operates on unary numeric function. But to make things more readable we can define new types for unary and binary numeric functions:

(define-type UnaryFun  = (Number -> Number))
(define-type BinaryFun = (Number Number -> Number))

(: binop->fbinop : BinaryFun -> (UnaryFun UnaryFun -> UnaryFun))
;; turns an arithmetic binary operator to a function operator
(define (binop->fbinop op)
  (lambda (f g)
    (lambda (x) (op (f x) (g x)))))

(: fsub : UnaryFun UnaryFun -> UnaryFun)
;; functional pointwise subtraction
(define fsub (binop->fbinop -))

We can do this with anything — developing a rich library of functions and functionals (functions over functions) is extremely easy… Here’s a pretty extensive yet very short library of functions:

#lang pl untyped

(define (currify f)
  (lambda (x) (lambda (y) (f x y))))
(define (binop->fbinop op)
  (lambda (f g)
    (lambda (x) (op (f x) (g x)))))
(define (compose f g)
  (lambda (x) (f (g x))))

(define dx 0.01)
(define (deriv f x)
  (/ (- (f (+ x dx)) (f x)) dx))
(define (integrate f x)
  (define over? (if (< x 0) < >))
  (define step  (if (< x 0) - +))
  (define add  (if (< x 0) - +))
  (define (loop y acc)
    (if (over? y x)
      (* acc dx)
      (loop (step y dx) (add acc (f y)))))
  (loop 0 0))

(define fadd (binop->fbinop +))
(define fsub (binop->fbinop -))
(define fmul (binop->fbinop *))
(define fdiv (binop->fbinop /))
(define fderiv    (currify deriv))
(define fintegrate (currify integrate))
;; ...

This is written in the “untyped dialect” of the class language, but it should be easy now to add the types.

Examples:

;; want to verify that `integrate' is the opposite of `deriv':
;;  take a function, subtract it from its derivative's integral
(plot (fsub sin (fintegrate (fderiv sin))))

;; want to magnify the errors? -- here's how you magnify:
(plot (compose ((currify *) 5) sin))

;; so:
(plot (compose ((currify *) 20)
              (fsub sin (fintegrate (fderiv sin)))))

Tuesday, January 28th Side-note: “Point-Free” combinators

Forming functions without using lambda (or an implicit lambda using a define syntactic sugar) is called point-free style. It’s especially popular in Haskell, where it is easier to form functions this way because of implicit currying and a large number of higher level function combinators. If used too much, it can easily lead to obfuscated code.

Tuesday, January 28th This is not Runtime Code Generation

All of this is similar to run-time code generation, but not really. The only thing that fderiv does is take a function and store it somewhere in the returned function, then when that function receives a number, it uses the stored function and send it to deriv with the number. We could simply write deriv as what fderiv is — which is the real derivative function:

(define (deriv f)
  (lambda (x)
    (/ (- (f (+ x dx)) (f x)) dx)))

but again, this is not faster or slower than the plain deriv. However, there are some situations where we can do some of the computation on the first-stage argument, saving work from the second stage. Here is a cooked-to-exaggeration example — we want a function that receives two inputs x, y and returns fib(x)*y, but we must use a stupid fib:

(define (fib n)
  (if (<= n 1)
    n
    (+ (fib (- n 1)) (fib (- n 2)))))

The function we want is:

(define (bogus x y)
  (* (fib x) y))

If we currify it as usual (or just use currify), we get:

(define (bogus x)
  (lambda (y)
    (* (fib x) y)))

And try this several times:

(define bogus36 (bogus 36))
(map bogus36 '(1 2 3 4 5))

But in the definition of bogus, notice that (fib x) does not depend on y — so we can rewrite it a little differently:

(define (bogus x)
  (let ([fibx (fib x)])
    (lambda (y)
      (* fibx y))))

and trying the above again is much faster now:

(define bogus36 (bogus 36))
(map bogus36 '(1 2 3 4 5))

This is therefore not doing any kind of runtime code generation, but it enables doing similar optimizations in our code. A proper RTCG facility would recompile the curried function for a given first input, and (hopefully) automatically achieve the optimization that we did in a manual way.