PL: Lecture #5  Tuesday, September 24th

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

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

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.