1 The Basics
2 Lists
3 Defining Our Own Datatypes
4 S-Expressions

CSE P 505 Spring 2013 Lecture #1 Code Examples

Some quick notes on what we did in the first lecture with Racket.

1 The Basics

The interactions pane lets you evaluate one-off expressions. For example:

> "hello world"
- string
"hello world"
> 3
- number
3
> (+ (* 2 3) (- 7 4))
- number
9

The definitions pane is for defining types, values, and test cases that you’ll use with interactions and other definitions. For example:

; An approximation of the constant π.
(define π 3.14159265)
 
; Returns the square of x.
(define (sqr x)
  (* x x))
 
; Returns the area of a circle with the given radius.
(define (circle-area radius)
  (* π (sqr r)))
 
; A test case for circle-area.
(test (circle-area 5) (* 25 π))

A simple conditional (i.e., if) expression selects between two code branches. (Only one of the branches is evaluated.)

; Returns the absolute value of x.
(define (abs x)
  (if (< x 0)
      (* -1 x)
      x))
 
(test (abs -3) 3)

The symbol type can be convenient for representing symbolic constants.

> 'saturday
- symbol
’saturday
> 'is-anyone-out-there?
- symbol
’is-anyone-out-there?

A case expression matches a symbol against sets of alternatives, with an optional else clause to handle the case of no match.

(define (type-of-day day)
  (case day
    [(saturday sunday) 'weekend]
    [(monday tuesday wednesday thursday friday) 'weekday]
    [else (error 'type-of-day "not a day")]))
 
(test (type-of-day 'sunday) 'weekend)
(test (type-of-day 'thursday) 'weekday)
(test/exn (type-of-day 'caturday) "type-of-day: not a day")

Note the use of test/exn to define a test case where the expected outcome is an error. What happens if we omit the else clause in type-of-day?

2 Lists

A list can be either empty or non-empty. Empty lists are written empty, and non-empty lists are constructed with cons, which takes two arguments: 1) an item to go at the front of the list, and 2) the rest of the list.

> empty
- (listof ’a)
’()
> (cons 'bird empty)
- (listof symbol)
’(bird)
> (cons 1 (cons 2 (cons 3 empty)))
- (listof number)
’(1 2 3)

We didn’t go into detail about this, but (in plai-typed) lists are said to be a parametric datatype. Loosely speaking, this means we can construct lists that hold whatever type of data we like. However, a given list can only contain elements of one type.

> (cons 1 (cons 'bird empty))
typecheck failed: number vs symbol ...

What do the following expressions mean?

> (cons empty empty)
???
> (cons (cons 'ant empty) empty)
???
> (cons empty (cons 'ant empty))
???

The list operator is a convenient way to create longer lists.

(define a-list (list 1 2 3 4 5))
 
> a-list
- (listof number)
’(1 2 3 4 5)
> (list 'ant 'bird 'cat 'dog)
- (listof symbol)
’(ant bird cat dog)

The manner of displaying list values with the quote character () is a bit confusing. For now, think of quote as being "viral", so the quote around the list applies implicitly to the symbols inside it also.

Lists are defined inductively, so recursion is usually the natural way to process them. For example, we defined functions to compute the length of a list, and whether a list contains an element.

; Returns the length of a-list.
(define (length a-list)
  (if (empty? a-list)
      0
      (add1 (length (rest a-list)))))
 
(test (length empty) 0)
(test (length (list 'ant 'bird 'cat)) 3)
 
; Returns whether a-list contains a-thing.
(define (contains? a-list a-thing)
  (cond
    [(empty? a-list) false]
    [(eq? (first a-list) a-thing) true]
    [else (contains? (rest a-list) a-thing)]))
 
(test (contains? (list 1 2 3) 2) true)
(test (contains? (list 1 2 3) 4) false)

3 Defining Our Own Datatypes

We defined a binary tree that held numbers at the leaves. The define-type form defines constructors, accessors, and discriminators for the variants of the new type.

(define-type Tree
  [leaf (value : number)]
  [node (left : Tree) (right : Tree)])
 
(define a-tree
  (node (leaf 5) (leaf 7)))
 
> (node? a-tree)
- boolean
#t
> (leaf? a-tree)
- boolean
#f
> (leaf? (node-left a-tree))
- boolean
#t
> (leaf-value (node-left a-tree))
- number
5

We wrote a function to sum the values in a tree, using type-case to do one-level pattern-matching on the tree variants.

(define (sum a-tree)
  (type-case Tree a-tree
    [leaf (value) value]
    [node (left right) (+ (sum left) (sum right))]))

To make things more interesting, we changed the definition of Tree to let the nodes specify how they want their children combined.

(define-type Tree
  [leaf (value : number)]
  [+node (left : Tree) (right : Tree)]
  [*node (left : Tree) (right : Tree)])
 
(define my-tree
  (+node (*node (leaf 3) (leaf 4))
         (*node (leaf 6) (leaf 10))))

We extended sum to add or multiply according to the type of node, and we changed its name.

(define (interp a-tree)
  (type-case Tree a-tree
    [leaf (n) n]
    [+node (left right) (+ (interp left) (interp right))]
    [*node (left right) (* (interp left) (interp right))]))
 
(test (interp my-tree) 72)

4 S-Expressions

By now, we’re close to having a (very simple) programming language. (Ok, it’s missing a few key features...) However, we’d like a way of writing "programs" that’s more natural to read and write than constructing trees by hand.

We saw that quoting (i.e., putting a single-quote character in front of) an identifier creates a symbol. Quoting other types of data, such as numbers, strings, or lists of (lists of ...) these, creates s-expressions ("symbolic expressions"). The s-expression datatype provides a convenient way of representing syntax similar to what Racket uses, and it allows us to parse this syntax into an even more structured form in a relatively straightforward manner.

(define my-exp
  '(+ (* 2 3) (* 4 5)))
> my-exp
- s-expression
’(+ (* 2 3) (* 4 5))
> '3
- s-expression
3

There are several predicates available to determine which type of datum an s-expression represents, like s-exp-list?, s-exp-number?, and s-exp-symbol?. Only one of these will return true, in which case the s-exp->list, s-exp->number, and s-exp->symbol operators (respectively) will cast it to the corresponding type.

Below is an example of using these operators to parse a suitably formatted s-expression into our Tree type. This is a bit longer than what we wrote in class, mainly because it contains comments and does more extensive error checking and reporting.

; Parses s-exp and returns an equivalent Tree.
(define (parse s-exp)
  (cond
    [(s-exp-list? s-exp)
     ; We have a list-like s-expression.  Call s-exp->list to get back a
     ; list of s-expressions that we can analyze.
     (let ([lst (s-exp->list s-exp)])
       (cond
         ; Since we only support simple arithmetic expressions for now,
         ; the list had better contain exactly three things: an operator
         ; and two arguments.
         [(= 3 (length lst))
          ; The operator needs to be either + or *.
          (case (s-exp->symbol (first lst))
            [(+) (+node (parse (second lst))
                        (parse (third lst)))]
            [(*) (*node (parse (second lst))
                        (parse (third lst)))]
            [else (error 'parse (string-append "unknown operator "
                                               (to-string (first lst))))])]
         [else (error 'parse (string-append "syntax error in "
                                            (to-string lst)))]))]
    ; If not a list, then for now we'd better have a number.
    [(s-exp-number? s-exp) (leaf (s-exp->number s-exp))]
    [else (error 'parse (string-append "syntax error in " (to-string s-exp)))]))
 
; (Woefully inadequate) test cases for the parser and interpreter.  :)
(test (interp (parse my-exp)) 26)
(test/exn (interp (parse '(bad syntax))) "syntax error")