CSE 341: Scheme and dynamic typing, supplementary notes

Misc. special forms

Expression sequences: begin

The begin expression sequence special form works much like the expression-sequence construct (expr1; ...; exprn)in ML: it evaluates each expression passed to it in sequence, and returns the value of the last expression.

(+
  (begin
    (display "hi")
    (newline)
    (display "bye")
    (newline)
    4)
  5)            ; => 9 (prints "hi", newline, "bye", newline as a side effect)

The truth about expression sequences

Previously, for simplicity, we said that special forms like cond and let* take single expressions for bodies. Actually, most special forms can take expression sequences in the "body-part" even without using begin. For example:

(let* ((a 5)
       (b (+ a a)))
  (display b)
  (+ b b))

Testing against sets of values: case

The case construct has the syntax

(case testExpr
    clause1
    clause2
    ...
    clausen)

where each clausei has the form

((keyi1 ... keyim) exprSeq

Each keyij is one value to be matched against. If the value of testExpr is equal to any of the keys for a clause, then the expressions in exprSeq will be evaluated sequentially and the last one returned. For example:

(define x 4)
(case x
  ((1 2 3) "one to three")
  ((4 5 6) "four to six")
  (else    "really big"))    ; => "four to six"

A few uses of dynamic typing

This section demonstrates a few more uses of Scheme's dynamic typing that would be cumbersome to simulate in an ML-like language.

More vararg functions

Scheme functions take variable numbers of arguments using "vararg" syntax:

(define swap-first-two
    (lambda (first second . rest)
        (list-append (cons second (cons first rest)))))

The apply function applies any function to an list as an argument:

(apply + '(1 2 3 4))  ; => 10

The map function takes any number of lists, and applies its function argument to all elements in parallel:

(map (lambda (x y) (+ x y)) '(1 2 3 4) '(5 6 7 8))   ; => '(6 8 10 12)

We define my-map, with the same behavior as the built-in map, as follows (we use two helper functions to collect the heads and tails of a list of lists):

(define (first-members argLists)
  (if (null? argLists)
      ()
      (cons (caar argLists)
            (first-members (cdr argLists)))))

(define (rest-members argLists)
  (if (null? argLists)
      ()
      (cons (cdar argLists)
            (rest-members (cdr argLists)))))

;; Definition of my-map
(define (my-map f . argLists)
  (if (null? (car argLists))
      ()
      (cons (apply f (first-members argLists))
            (apply my-map f (rest-members argLists)))))

Objects as lambdas

In an object-oriented language, an object contains a list of bindings from names to values --- it so happens that these bindings are called fields. A lexically scoped closure also contains a list of bindings --- the captured environment, including the values of locals. Using lambdas and varargs, we can get a sort-of-satisfactory simulation of objects in Scheme:

(define (make-point x y)
   (lambda (method . args)
      (case method 
         ((get-x) x)
         ((get-y) y)
         ((set-x!) (set! x (car args)))
         ((set-y!) (set! y (car args)))
         (else 'invalid-method-error))))

;; Usage
(define my-point (make-point 1 2))   ; => #>procedure ...>
(my-point 'get-x)                    ; => 1
(my-point 'get-y)                    ; => 2
(my-point 'set-x! 3)                 ; => #<void>
(my-point 'get-x)                    ; => 3

The syntax for defining objects is somewhat worse than we'd like, but one can use syntax macros.

Suggested exercises