Introduction to Scheme

Scheme is a popular dialect of Lisp, a language developed in the 1950s and 1960s by John McCarthy and his students. Lisp is based on the lambda calculus, a mathematical formalism for defining and executing functions.

Despite being so simple and not quite having achieved mainstream success on its own, Lisp has proven to be an extremely flexible and powerful language, and has been the source of ideas found in many other languages.

Getting Scheme

These notes will be using MIT Scheme, a popular version of Scheme that is easy to use.

To install MIT Scheme on Ubuntu Linux, run this command in a shell:

$  sudo apt-get install mit-scheme

For other systems, see the MIT Scheme home page.

Running Scheme

Once it’s installed, you can run it at the command-line like this:

$ mit-scheme

MIT/GNU Scheme running under GNU/Linux
Type `^C' (control-C) followed by `H' to obtain information about interrupts.

Copyright (C) 2011 Massachusetts Institute of Technology
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Image saved on Tuesday October 22, 2013 at 12:31:09 PM
  Release 9.1.1 || Microcode 15.3 || Runtime 15.7 || SF 4.41 || LIAR/i386 4.118
  Edwin 3.116

1 ]=>

]=> is the interpreter prompt, and means that it is waiting for you to type something. So try this:

1 ]=> (* 2 3 5)

;Value: 30

1 ]=>

The Scheme expression (* 2 3 5) calculates the product of 2, 3, and 5, i.e. \(2 \cdot 3 \cdot 5 = 30\).

While it is possible to compile Scheme, we will stick to just the interpreter for this course.

Quitting Scheme

To exit the interpreter, type ctrl-d:

1 ]=>


Kill Scheme (y or n)? Yes
Moriturus te saluto.

Or by typing (exit) and return:

1 ]=> (exit)


Kill Scheme (y or n)? Yes
Moriturus te saluto.

Note

The ending message “Moriturus te saluto.” is Latin for “I who am about to die salute you.”

Loading Files

We’ll usually be putting Scheme functions in text files and then loading that text file into the interpreter. For example, suppose the file circle.scm contains this:

;;; (circle-perim r) calculates perimeter of a circle of radius r
(define circle-perim
    (lambda (radius)
        (* 2 3.14 radius)
    )
)

Then in Scheme we can load and run it like this:

1 ]=> (load "circle.scm")

1 ]=> (circle-perim 2)

;Value: 12.56

1 ]=>

Using Scheme in an Editor

While you can use MIT-Scheme directly at the command-line, it lacks many basic editing features and so is a very unpleasant experience. Most programmers prefer to run it directly in their text editor.

For example, if you are using Sublime Text as your editor, then you can install the SublimeREPL package, which lets you run an interpreter in a Sublime window.

Another popular editor for Lisp-like languages is Emacs, which comes already to run Scheme in an editor window.

Using Scheme’s Interactive Interpreter

We’ll mostly be using Scheme via its interpreter, or REPL. REPL stands for read-eval-print loop, and it lets us evaluate Scheme expressions one at a time. For instance:

1 ]=> (+ 3 2)

;Value: 5

1 ]=> (* 10 4)

;Value: 40

1 ]=> (- 5 8)

;Value: -3

1 ]=> (/ 5 2)

;Value: 5/2

1 ]=> (/ 6 2)

;Value: 3

1 ]=> (/ 5 2)

;Value: 5/2   ;; 5/2 is a rational value

Right away you can see that Scheme is a bit different than most languages. All functions in Scheme are called using prefix notation. This can seem strange, at first, especially for arithmetic. Most languages use infix notation for arithmetic expressions, e.g. 3 + 2, since that’s we learn in elementary school. However, when we evaluate functions, we normally use prefix notation, e.g. f(2, 3). Scheme uses prefix notation for everything, which, once you get used to it, is simple and consistent.

The other interesting feature of Scheme notation is that expressions are always delineated by open ( and closed ) brackets. As you will see, Scheme programs can end up having a lot of brackets, and making sure they all match can be tricky.

You can pass multiple arguments to arithmetic operations:

=> (+ 1 2 3)
6
=> (* 1 6 2 2)
24
=> (/ 100 10 5)
2
=> (- 1 2 3)
-4

Or combine numbers and expressions:

=> (+ (- 1 2 3) (* 4 5))
16

Notice that Scheme supports a rational numeric type, which is not common in mainstream languages:

1 ]=> (+ 1/2 1/2)

;Value: 1

1 ]=> (+ 1/2 1/2 1/2)

;Value: 3/2

Number Basics

Lets look at the basic types of values in Scheme.

The standard Scheme functions integer?, rational?, real?, complex?, and number? can be used to test for various kinds of numbers. For example:

;;
;; integers
;;
1 ]=> (integer? 5)

;Value: #t

1 ]=> (integer? 6.0)

;Value: #t

1 ]=> (integer? 6.1)

;Value: #f

;;
;; rationals
;;
1 ]=> (rational? 6/3)

;Value: #t

1 ]=> (rational? 6)

;Value: #t

1 ]=> (rational? 6.1)

;Value: #t

;;
;; reals
;;
1 ]=> (real? 2)

;Value: #t

1 ]=> (real? 2.1)

;Value: #t

1 ]=> (real? 6/17)

;Value: #t


;;
;; complex
;;
1 ]=> (complex? 3.1+4i)

;Value: #t

1 ]=> (complex? 3.1+0i)

;Value: #t

1 ]=> (complex? 6)

;Value: #t

The value #t means true, and #f means false. The number? function can be used to test if a value is any kind of number at all.

Here are a few handy built-in numerical functions: zero?, positive?, negative?, odd?, even?. For example:

1 ]=> (zero? (- 5 5))

;Value: #t

1 ]=> (positive? (- 5 5))

;Value: #f

1 ]=> (positive? 22)

;Value: #t

1 ]=> (negative? 0)

;Value: #f

1 ]=> (negative? -3)

;Value: #t

1 ]=> (odd? 101)

;Value: #t

1 ]=> (even? 2038)

;Value: #t

There’s also min and max:

1 ]=> (min 8 3 -2 9 1 0)

;Value: -2

1 ]=> (max 8 3 -2 9 1 0)

;Value: 9

See the MIT Scheme documentation for a list of the other mathematical functions.

Symbol Basics

Symbol are a kind of value that is not found in many other mainstream languages. Use symbol? to test if a value is a symbol:

1 ]=> (symbol? 'a)

;Value: #t

1 ]=> (symbol? 'apple)

;Value: #t

1 ]=> (symbol? 'first-ever-airplane)

;Value: #t

1 ]=> (symbol? 4)

;Value: #f

1 ]=> (symbol? odd?)

;Value: #f

1 ]=> (symbol? x)

;Unbound variable: x

While they may look like strings, they are not. Symbols are just values with names, and we don’t usually care about their individual characters. Like other Scheme names, symbol names cannot contain certain characters (such as spaces).

Note the ' in front of all the valid symbols. Quoting is an important part of Scheme, and it is necessary to distinguish a symbol from a variable. For example, x is a variable, while 'x' is a symbol:

1 ]=> (symbol? 'x)

;Value: #t

1 ]=> (symbol? x)

;Unbound variable: x

The expression (symbol? x) does not evaluate because Scheme does not know about the variable x.

Like numbers, symbols evaluate to themselves:

1 ]=> 'a

;Value: a

1 ]=> 'cat

;Value: cat

This contrasts with variables, which evaluate to the value they’re bound to.

define is one way to create new variables:

1 ]=> (define pi 3.14)

;Value: pi

1 ]=> pi

;Value: 3.14

1 ]=> (* 2 pi)

;Value: 6.28

Here, pi is a variable bound to the value 3.14. Thus, pi evaluates to 3.14, and can be used anywhere a number is needed.

Notice also that the variable pi does not have a type. This is a key feature of Scheme: it is a weakly typed language, meaning that the type of most expressions is not known until the expression is evaluated (in contrast to more strongly typed languages where types are often known at compile-time).

pi is not a symbol, it’s a variable:

1 ]=> (symbol? pi)    ;; pi is a variable

;Value: #f

1 ]=> (symbol? 'pi)   ;; 'pi is a symbol

;Value: #t

Basics of Lists

A list is a sequence of 0, or more, values. The values an in a list can be any Scheme value: numbers, symbols, other lists, etc.

As with symbols, literal lists usually begin with a ':

1 ]=> '(1 2 3)

;Value 13: (1 2 3)

1 ]=> '(my dog has fleas)

;Value 14: (my dog has fleas)

1 ]=> '(my dog (and hamster) have 10 fleas each))

;Value 15: (my dog (and hamster) have 10 fleas each)

The empty list is a list with 0 values in it, and is written as either () or '(). Use null? to test if a list is empty:

1 ]=> (null? '(1 2))

;Value: #f

1 ]=> (null? '())

;Value: #t

If you try to evaluate a list with the ' at the start, you will sometimes get an error:

1 ]=> (my dog has fleas)

;Unbound variable: fleas

By default, Scheme treats the first element of a list as a function, and the remaining elements as inputs to that function. Since my is not bound to a function (or anything at all!), Scheme reports an error.

Compare it to this example:

1 ]=> (min 3 1)

;Value: 1

(min 3 1) is an unquoted list, so Scheme assumes it is a function call. It replaces the variable min with the function it is bound to, and then passes 3 and 1 to it as parameters.

If you put a ' in front of this list, then it evaluates to itself:

1 ]=> '(min 3 1)

;Value 16: (min 3 1)

Note

Quoted lists are similar in spirit to strings in other languages. For example, in Go, this statement prints 3:

fmt.Println(1 + 2)

But this statement prints "1 + 2":

fmt.Println(“1 + 2”)

The difference is that, on its own, 1 + 2 is an expression that evaluates to 3. But put it inside quotes, then it’s just a string of 5 characters that are not evaluated.

Quoted lists are the same idea: the unquoted expression (+ 1 2) evaluates to 3, while the quoted expression '(+ 1 2) evaluates to the list (+ 1 2).

Scheme lists are essentially singly-linked lists, and so that has a big impact on their performance characteristics.

List Processing

Scheme has a number of useful built-in list processing functions. When thinking about the performance characteristics of these functions, keep in mind that Scheme lists are essentially singly-linked lists.

The car function returns the first element of a list:

1 ]=> (car '(1 2 3))

;Value: 1

1 ]=> (car '((1 2) 3 4))

;Value 18: (1 2)

1 ]=> (car '(my dog has fleas))

;Value: my

1 ]=> (car '())

;The object (), passed as the first argument to car, is not the correct type.

The empty list has no first element, so (car '()) is an error.

The cdr function returns everything on a list except for the first element:

1 ]=> (cdr '(1 2 3))

;Value 19: (2 3)

1 ]=> (cdr '((1 2) 3 4))

;Value 20: (3 4)

1 ]=> (cdr '(my dog has fleas))

;Value 21: (dog has fleas)

1 ]=> (cdr '())

;The object (), passed as the first argument to cdr, is not the correct type.

Note

The names car and cdr are traditional, and refer to the underlying hardware of the original computer on which Lisp was first implemented.

Some languages use the name first instead of car, and rest instead of cdr.

The cons function returns a new list by adding a new element to the start of the list:

1 ]=> (cons 1 '(2 3))

;Value 22: (1 2 3)

1 ]=> (cons '(2 3) '(3 4))

;Value 23: ((2 3) 3 4)

1 ]=> (cons 'my '(dog has fleas))

;Value 24: (my dog has fleas)

1 ]=> (cons 'x '())

;Value 25: (x)

car, cdr, and cons work well together, e.g.:

1 ]=> (cons (car '(1 2 3)) (cdr '(1 2 3)))

;Value 26: (1 2 3)

It’s sometimes useful to think of a list as a nested series of calls to cons. For example, the list '(a b c d) could be written like this:

1 ]=> (cons 'a (cons 'b (cons 'c (cons 'd '()))))

;Value 27: (a b c d)

Recall that because Scheme implements lists as singly-linked lists, then car, cdr, and cons all run very quickly, no matter the size of the list: they all run in worst-case \(O(1)\) time.

Another convenient way to create a list is to use the list function:

1 ]=> (list 1 2 3)

;Value 28: (1 2 3)

1 ]=> (list '(1 2) 3 4)

;Value 29: ((1 2) 3 4)

1 ]=> (list 'my 'dog 'has 'fleas)

;Value 30: (my dog has fleas)

1 ]=> (list)

;Value: ()

These functions can be combined to many different useful things. For example, the car of a lists cdr is its second element:

1 ]=> (car (cdr '(a b c d)))

;Value: b

Be aware that Scheme expressions are quite general, so you can even do things like this:

1 ]=> ((car (list min max)) 41 2 3)

;Value: 2

To evaluate this entire expression, (car (list min max)) is evaluated. First, the sub-expression (list min max) evaluates to (list <min-fn> <max-fn>), i.e. the variables min and max are replaced by the functions they’re bound to. Then (list <min-fn> <max-fn>) evaluates to the list (<min-fn> <max-fn>). So (car (list min max)) then becomes (car (<min-fn> <max-fn)), which is just <min-fn>.

Functions

Functions are the heart of Lisp, and there are a number of ways to create them. One way is to use defn:

(define add1
    (lambda (n)
        (+ n 1)
    )
)

This defines a function named add1 that takes a single value, n, as input. It returns the value of (+ 1 n):

1 ]=> (add1 5)

;Value: 6

1 ]=> (add1 (add1 (add1 3)))

;Value: 6

Notice the type of n is not specified. Scheme is dynamically typed: it doesn’t check the type of n until run-time.

Here’s another function:

(define circle-area
    (lambda (radius)
        (* 3.14 radius radius)
    )
)

1 ]=> (circle-area 3)

;Value: 28.259999999999998

1 ]=> (circle-area (add1 4))

;Value: 78.5

In the following function, p1 and p2 are assumed to both be of the form (x y):

(define add-points
    (lambda (p1 p2)
        (list (+ (first p1) (first p2))
              (+ (second p1) (second p2))
        )
    )
)

1 ]=> (add-points '(2 1) '(5 9))

;Value 11: (7 10)

Logical Expressions

You can use and, or, and not to evaluate logical expressions in Scheme. For example:

(define all-diff?
    (lambda (x y z)
        (and (not (equal? x y))
             (not (equal? x z))
             (not (equal? y z))
        )
    )
)

1 ]=> (all-diff? 1 2 1)

;Value: #f

1 ]=> (all-diff? 1 2 -5)

;Value: #t

and is what is known in Scheme as a special form. It is different than an ordinary function because it does not immediately evaluate the expressions passed to it. Instead, and evaluates the expression one at a time from left to right, and stops immediately after the first expression that evaluates to false; none of the expressions after this are evaluated.

or is also a special form, e.g.:

(define any-same?
    (lambda (x y z)
        (or (equal? x y)
            (equal? x z)
            (equal? y z)
        )
    )
)

1 ]=> (any-same? 3 5 3)

;Value: #t

1 ]=> (any-same? 3 5 13)

;Value: #f

Similar to and, the or special form evaluates its inputs one at a time, from left to right, stopping after finding the first true expression (and not evaluating anything after that).

not works as expected, i.e. it flips true to false and false to true:

(define any-same2?
    (lambda (x y z)
        (not (all-diff? x y z))
    )
)

Decisions with cond

cond is a special form in Scheme that it is similar to if-else structures in other languages. Consider this function:

(define sign
    (lambda (n)
        (cond ((< n 0) 'negative)
              ((= n 0) 'zero)
              (#t      'positive)
        )
    )
)

1 ]=> (sign -4)

;Value: negative

1 ]=> (sign 0)

;Value: zero

1 ]=> (sign 5)

;Value: positive

A cond expression consists of a number of 2-element (test value) lists. For example, (< n 0) is a test, and 'negative is it’s corresponding value.

cond evaluates its pairs one at a time in left to right order. If a test evaluates to true, its corresponding value is returned, and the which always evaluates to true, and so its corresponding value is always returned if it’s reach. It serves the same purpose as an else clause in an if-else statement.

Instead of #t in the final test, you can also use else:

(define sign
    (lambda (n)
        (cond ((< n 0) 'negative)
              ((= n 0) 'zero)
              (else    'positive)
        )
    )
)

Recursive Functions

If you want to determine the length of a list, Scheme provides a built-in length function:

1 ]=> (length '(1 (2 3) 4))

;Value: 3

We could also write our own function using recursion like this:

(define len
    (lambda (lst)
        (cond
            ((null? lst) 0)             ;; base case
            (#t (+ 1 (len (cdr lst))))  ;; recursive case
        )
    )
)

len has two cases:

  • base case: when the list lst is empty
  • recursive case: calculate the length of the cdr of lst, then add 1

As you can see, you must take care to get all the parentheses to match!

Here’s another good example of recursion:

;; Returns true if x is in lst, and false otherwise.
(define member
    (lambda (x lst)
        (cond
            ((null? lst)          #f)
            ((equal? x (car lst)) #t)
            (#t                   (member x (cdr lst)))
        )
    )
)

Recall that the symbol? functions tests if an object is a symbol (such as 'cat). The following function returns the number of symbols in a list:

(define count-sym
    (lambda (lst)
        (cond
            ((null? lst)         0)
            ((symbol? (car lst)) (+ 1 (count-sym (cdr lst))))
            (#t                  (count-sym (cdr lst)))
        )
    )
)

If instead we want to count how many numbers are in a list, a small modification to count-sym is all that’s needed:

(define count-num
    (lambda (lst)
        (cond
            ((null? lst)         0)
            ((number? (car lst)) (+ 1 (count-num (cdr lst))))
            (#t                  (count-num (cdr lst)))
        )
    )
)

Compared to count-sym, count-num is the same except for two differences: number? is used instead of symbol?, and each occurrence of count-sym has been changed to count-num.

So what we can do is write a general-purpose counting function that takes a list and a predicate function (i.e. a function that takes one value as input and returns a boolean value) as input:

(define count-fn
    (lambda (fn? lst)
        (cond
            ((null? lst)     0)
            ((fn? (car lst)) (+ 1 (count-fn fn? (cdr lst))))
            (#t              (count-fn fn? (cdr lst)))
        )
    )
)

To use it we pass in any function that takes a single input value and returns either true or false:

1 ]=> (count-fn even? '(1 2 3 4 5 6 7))

;Value: 3

1 ]=> (count-fn odd? '(1 2 3 4 5 6 7))

;Value: 4

1 ]=> (count-fn number? '(1 2 3 4 5 6 7))

;Value: 7

1 ]=> (count-fn symbol? '(1 2 3 4 5 6 7))

;Value: 0

Using count-fn, it is easy to re-write the previous functions like this:

(define count-symbol (lambda (lst) (count-fn symbol? lst)))

(define count-number (lambda (lst) (count-fn number? lst)))

More Examples

;;
;; Remove all top-level occurrences of x from lst.
;;
(define remove-all
    (lambda (x lst)
        (cond
            ((null? lst)
                '())
            ((equal? x (car lst))
                (remove-all x (cdr lst)))
            (else
                (cons (car lst) (remove-all x (cdr lst))))
        )
    )
)

;;
;; Replace all top-level occurrences of 'clinton with 'trump.
;;
(define trumpify
    (lambda (lst)
        (cond
            ((null? lst)
                '())
            ((equal? 'clinton (car lst))
                (cons 'trump (trumpify (cdr lst))))
            (else
                (cons (car lst) (trumpify (cdr lst))))
        )
    )
)

;;
;; Replace all top-level occurrences of old with new.
;;
(define replace
    (lambda (old new lst)
        (cond
            ((null? lst)
                '())
            ((equal? old (car lst))
                (cons new (replace old new (cdr lst))))
            (else
                (cons (car lst) (replace old new (cdr lst))))
        )
    )
)

;;
;; Returns a function that, when called, replaces all occurrences of old
;; with new on a given list.
;;
(define make-replacer
    (lambda (old new)
        (lambda (lst) (replace old new lst))
    )
)

;;
;; Returns the number of numbers on lst, even numbers inside of lists.
;;
(define deep-count-num
    (lambda (lst)
        (cond
            ((null? lst)
                0)
            ((list? (car lst))
                (+ (deep-count-num (car lst)) (deep-count-num (cdr lst))))
            ((number? (car lst))
                (+ 1 (deep-count-num (cdr lst))))
            (else
                (deep-count-num (cdr lst)))
        )
    )
)

Lambda Functions

In Scheme, a lambda function is essentially a function without a name. We’ve been using lambda functions to define named functions, so you should be somewhat familiar with them.

Here’s a lambda function that adds 1 to its input:

(lambda (n) (+ n 1))

This function takes one input which it names n, and returns the value of n + 1. Notice that the function has no name! So to call it, you must do something like this:

((lambda (n) (+ n 1)) 5)

This passes 5 to the lambda function, and so the entire expression evaluates to 6.

Lambda functions are often a convenient way to write small functions. For example, suppose we want to count how many times 2 or 5 occurs in a list. Then we could do it like this:

(count-fn (lambda (n) (or (= n 2) (= n 5)))
          '(2 2 6 5 7 5 2 1)
)

Once you get the hang of them, using lambda functions in this way is often very convenient.

Here’s another example where lambda functions are useful. The keep-if function returns a list containing all the elements of a given list that match a given test function:

(define keep-if
    (lambda (pred? lst)
        (cond
            ((null? lst)
                '())
            ((pred? (car lst))
                (cons (car lst) (keep-if pred? (cdr lst))))
            (else
                (keep-if pred? (cdr lst)))
        )
    )
)

1 ]=> (keep-if even? '(1 2 3 4 5))

;Value 13: (2 4)

1 ]=> (keep-if (lambda (n) (or (= n 1) (even? n))) '(1 2 3 4 5))

;Value 14: (1 2 4)

Here’s an expression that partitions a list of numbers so that all the evens come before all the odds:

1 ]=> (append (keep-if even? '(1 2 3 4 5)) (keep-if odd? '(1 2 3 4 5)))

;Value 18: (2 4 1 3 5)

The append function appends two (or more) lists together to form a single list.

We can write this as a function:

(define parity-partition
    (lambda (nums)
        (append (keep-if even? nums) (keep-if odd? nums))
    )
)

1 ]=> (parity-partition '(1 2 3 4 5))

;Value 21: (2 4 1 3 5)

Note that Scheme has a built-in function called filter that does the same thing as keep-if.

Another useful function is called map: it applies a function to each element of a list. Scheme already has a map function built in, but lets write our own version called mymap:

(define mymap
    (lambda (f lst)
        (cond
            ((null? lst)
                '())
            (else
                (cons (f (car lst)) (mymap f (cdr lst))))
        )
    )
)

1 ]=> (mymap (lambda (n) (* n n)) '(1 2 3))

;Value 23: (1 4 9)

1 ]=> (mymap list '(1 2 3))

;Value 24: ((1) (2) (3))

Notice that the function, f, that is passed into mymap takes one item as input. The idea is that f is applied to every element of the list, e.g. (mymap f (a b c d)) returns ((f a) (f b) (f c) (f d)). Thus, it is similar to a loop in other languages.

Folding a List

Consider the following function for summing a list of numbers:

(define sum-list
    (lambda (nums)
        (cond
            ((null? nums)
                0)
            (else
                (+ (car nums) (sum-list (cdr nums))))
        )
    )
)

1 ]=> (sum-list '(1 4 2))

;Value: 7

Compare it to this function that calculates the product of a list of numbers:

(define prod-list
    (lambda (nums)
        (cond
            ((null? nums)
                1)
            (else
                (* (car nums) (prod-list (cdr nums))))
        )
    )
)

1 ]=> (prod-list '(1 4 2))

;Value: 8

sum-list and prod-list are very similar. There are three main differences. First, notice that sum-list returns 0 for an empty list, while prod-list returns 1. Second, in the recursive case, sum-list use + and prod-list uses *. Finally, the names of the recursive calls are different.

Now lets write a general-purpose function that implements this pattern, taking the base case value and function to be passed in as parameters:

(define my-fold
    (lambda (f empty-val lst)
        (cond
            ((null? lst)
                empty-val)
            (else
                (f (car lst) (my-fold f empty-val (cdr lst))))
        )
    )
)

1 ]=> (my-fold + 0 '(1 2 4))

;Value: 7

1 ]=> (my-fold * 1 '(1 2 4))

;Value: 8

In Scheme, there are a number of built-in functions similar to my-fold, such as reduce-left, reduce-right, fold-left, and fold-right. Each of these functions implements a variation of the same essential pattern as my-fold.

An interesting way to think about my-fold is to see what it does to a list when it is expanded into nested calls to cons. For example, we can write the list (1 4 2) in the form (cons 1 (cons 4 (cons 2 ()))). When you call my-fold on it, it as if each cons is replaced by f, and the empty list is replaced by empty-val. For example:

  (my-fold + 0 '(1 4 2))
= (my-fold + 0 (cons 1 (cons 4 (cons 2 ()))))
= (+ 1 (+ 4 (+ 2 0))
= 7

And:

  (my-fold * 1 '(1 4 2))
= (my-fold * 1 (cons 1 (cons 4 (cons 2 ()))))
= (* 1 (* 4 (* 2 1))
= 8

With this trick in mind, it is easy to see, for instance, that (my-fold cons () lst) returns lst itself.

It is also worth noting that the order in which my-fold evaluates its list is from right to left, i.e. the first things that get evaluated are the last element of lst and empty-val. Thus, this version of my-fold is sometimes called a right fold (sometimes abbreviated foldr).

For functions like + and * the order of evaluation doesn’t matter much, but it might for other functions. So another way to fold a list is from left to right (a left fold):

(define my-fold-left
    (lambda (f z lst)
        (cond
            ((null? lst)
                z)
            (else
                (my-fold-left f (f z (car lst)) (cdr lst)))
        )
    )
)

Here’s a trace:

  (my-fold-left + 0                   '(1 4 2))
= (my-fold-left + (+ 0 1)             '(4 2))
= (my-fold-left + (+ (+ 0 1) 4))      '(2))
= (my-fold-left + (+ (+ (+ 0 1) 4) 2) '())
= (+ (+ (+ 0 1) 4) 2)
= 7

An advantage of my-fold-left over my-fold (i.e. a right fold) is that the final function call of my-fold-left is a recursive call to my-fold- left itself. This is an instance of tail recursion, and it is important because Scheme automatically converts such recursion to a loop, thus avoiding the use of the call stack. In contrast, the last function called in my- fold function is to f, and so it must use the stack because there is no general-purpose way to replace its recursive calls with a loop that avoids using a linear amount of memory. Thus my-fold can run out of memory on large lists that my-fold-left can handle without a problem.

Let

An important and useful feature of Scheme is the let environment. Using let, you can bind values to variables. For example:

(define operator
    (lambda (e)
        (let ((op (car e))
             )
            (cond
                ((equal? op '+) 'addition)
                ((equal? op '-) 'subtraction)
                ((equal? op '*) 'multiplication)
                ((equal? op '/) 'division)
                (else 'unknown)
            )
        )
    )
)

1 ]=> (operator '(+ 1 2 3))

;Value: addition

1 ]=> (operator '(/ 1 2 3))

;Value: division

We assign the first element of the list e to op so that we don’t have to keep re-calculating it. Once a symbol is assigned in a let, it cannot be changed.

Here’s another example that evaluates numeric expressions of the form (left op right):

(define eval-basic
    (lambda (e)
        (let ((left (car e))
              (op (car (cdr e)))
              (right (car (cdr (cdr e))))
             )
            (cond
                ((equal? op '+) (+ left right))
                ((equal? op '-) (- left right))
                ((equal? op '*) (* left right))
                ((equal? op '/) (/ left right))
                (else 'err)
            )
        )
    )
)

1 ]=> (eval-basic '(1 + 2))

;Value: 3

1 ]=> (eval-basic '(1 - 2))

;Value: -1

Another example where a let-environment is in the deepmap function. deepmap is a variation on map that works like map, except the passed-in mapping function is applied to every non-list value on the list, even those nested inside lists. For example:

1 ]=> (deepmap 1+ '(1 (2 (1 1)) 3))

;Value 31: (2 (3 (2 2)) 4)

Here’s a definition for deepmap:

(define deepmap
    (lambda (f lst)
        (cond
            ((null? lst)
                ())
            (else
                (let* ((head (car lst))
                       (body (cdr lst))
                       (new_body (deepmap f body))
                      )
                    (cond
                        ((list? head)
                            (cons (deepmap f head) new_body))
                        (else
                            (cons (f head) new_body))
                    )
                )
            )
        )
    )
)

Notice that this uses let* instead of let. That’s because binding for new_body uses body, which is defined in the same let-environment. If we had used a regular let, then we’d get an error saying body is unbound. let* is designed specifically to allow already-bound variables to be used.

An Expression Evaluator

The eval-basic function given in the previous section only evaluates expressions of the form (num op num), where num is a number. But it is not much more work to evaluate any arithmetic expression of the form (expr op expr):

(define my-eval
    (lambda (e)
        (cond
            ((number? e)
                e)
            (else
                (let ((left (my-eval (car e)))
                      (op (car (cdr e)))
                      (right (my-eval (car (cdr (cdr e)))))
                     )
                    (cond
                        ((equal? op '+) (+ left right))
                        ((equal? op '-) (- left right))
                        ((equal? op '*) (* left right))
                        ((equal? op '/) (/ left right))
))))))  ;; closing brackets all on one live to save space


1 ]=> (my-eval '(2 * (2 + 3)))

;Value: 10

1 ]=> (my-eval '((10 - 5) / (2 * (2 + 3))))

;Value: 1/2

This is a recursive function: the expressions assigned to left and right both call my-eval recursively.

The expression (car (cdr e)) returns the second element of e. A short- hand way to write this is (cadr e); notice how the a and the d of cadr mirror the order of those letters in car and cdr. Similarly, the expression (car (cdr (cdr e))) can be re-written as (caddr e).

A Propositional Expression Evaluator

Lets write another evaluator, this time for infix propositional expressions like (t and (not f)). We will assume that the symbols t and f stand for true and false respectively, and that there no variables allowed in the expressions.

First, we define a series of functions that test if an object has a particular form:

(define is-literal?
    (lambda (e)
        (or (equal? e 't) (equal? e 'f))
    )
)

;; returns true iff lst is a list of length n
(define nlist
    (lambda (n lst)
        (and (list? lst)
             (= n (length lst))
        )
    )
)

;; (not expr)
(define is-not?
    (lambda (e)
        (and (nlist 2 e)
             (equal? 'not (car e))
        )
    )
)

;; (and expr)
(define is-and?
    (lambda (e)
        (and (nlist 3 e)
             (equal? 'and (cadr e))
        )
    )
)

;; (or expr)
(define is-or?
    (lambda (e)
        (and (nlist 3 e)
             (equal? 'or (cadr e))
        )
    )
)

These are all non-recursive functions that check that the top-level form of an expressions is correct. They don’t go into the sub-expressions.

Next, lets write a function that tests if an expression is a valid proposition:

;; Returns true iff e is a valid propositional expression.
(define is-expr?
    (lambda (e)
        (cond
            ((is-literal? e)
                #t)
            ((is-not? e)
                (is-expr? (cadr e)))
            ((is-and? e)
                (and (is-expr? (car e)) (is-expr? (third e))))
            ((is-or? e)
                (and (is-expr? (car e)) (is-expr? (third e))))
            ((is-nand? e)
                (and (is-expr? (car e)) (is-expr? (third e))))
            (else
                #f)
        )
    )
)

The idea of this function is to first check that e has a valid top-level form, and to then recursively check that its sub-expressions are also valid.

Next, lets write an evaluator that calculates whether the value of a valid proposition:

(define eval-prop-bool
    (lambda (e)
        (cond
            ((is-literal? e)
                (equal? e 't))
            ((is-not? e)  ;; (not expr)
                (not (eval-prop-bool (second e))))
            ((is-and? e)  ;; (expr and expr)
                (and (eval-prop-bool (first e)) (eval-prop-bool (third e))))
            ((is-or? e)   ;; (expr or expr)
                (or (eval-prop-bool (first e)) (eval-prop-bool (third e))))
            (else
                #f)
        )
    )
)

(define eval-prop
    (lambda (e)
        (if (eval-prop-bool e) 't 'f)
    )
)

For example:

1 ]=> (eval-prop '((not t) and (t or (not f))))

;Value: f

Simplifying a Propositional Expression

Something else we can do with a propositional expression is to simplify it. For example, any propositional expression of the form (not (not p)) can be simplified to p.

Lets write a simplifier that applies this rule to expressions:

;; double negation elimination
;; (not (not expr)) ==> expr
(define simplify
    (lambda (e)
        (cond
            ((is-literal? e)
                e)
            ;; (not (not e))
            ((and (is-not? e) (is-not? (second e)))
                (simplify (second (second e))))
            ((is-not? e)
                (list 'not (simplify (second e))))
            ((is-and? e)
                (list (simplify (first e)) 'and (simplify (third e))))
            ((is-or? e)
                (list (simplify (first e)) 'or (simplify (third e))))
            (else
                'error)
        )
    )
)

simplify first checks if e is of the form (not (not X)). If so, then it extracts the X (using (second (second e))) and returns the simplification of it. Otherwise, it tries to simplify the parts of the sub- expressions (if any) of e.

For example:

1 ]=> (simplify '((not (not t)) and (not (not t))))

;Value 34: (t and t)

Rewriting an Expression with Only nand

An basic fact about propositional logic is that any expression can be re- written using just the nand operator. The expression (p nand q) is true just when either p, or q, or both, are false. In other words, (p nand q) is equivalent to (not (p and q)).

Here are the rules for writing propositional expressions using just and:

  • (not p) is logically equivalent to (p nand p)
  • (p and q) is logically equivalent to ((p nand q) nand (p nand q))
  • (p or q) is logically equivalent to ((p nand p) nand (q nand q))

These rules are enough to transform any propositional expression into a logically equivalent one using only nand.

Here’s some code that does the transformation:

(define is-prop-literal?
    (lambda (x)
        (symbol? x)
    )
)

(define make-nand
    (lambda (p q)
        (list p 'nand q)
    )
)

;; Rewrites propositional logic expression e to a logically equivalent
;; expression that uses "nand" as the only connective.
(define nand-rewrite
    (lambda (e)
        (cond
            ((is-prop-literal? e)
                e)
            ((is-not? e)
                (let ((np (nand-rewrite (second e)))
                     )
                     (make-nand np np)))
            ((is-and? e)
                (let* ((p (nand-rewrite (first e)))
                       (q (nand-rewrite (third e)))
                       (pnq (make-nand p q))
                      )
                    (make-nand pnq pnq)))
            ((is-or? e)
                (let* ((p (nand-rewrite (first e)))
                       (q (nand-rewrite (third e)))
                       (pnp (make-nand p p))
                       (qnq (make-nand q q))
                      )
                    (make-nand pnp qnq)))
        )
    )
)

1 ]=> (pp (nand-rewrite '((p and q) or ((not p) or q))))
((((p nand q) nand (p nand q)) nand ((p nand q) nand (p nand q)))
 nand
 ((((p nand p) nand (p nand p)) nand (q nand q))
  nand
  (((p nand p) nand (p nand p)) nand (q nand q))))

Note the use of the pp function: it “pretty prints” a Scheme expression, making it is easier for humans to read.

Lisp is often a good choice for writing language-processing code as in the last couple of examples. Non-recursive versions of these functions would be much harder to write and understand. Thus, Lisp is often a good language for experimenting with new programming ideas, especially if they can be implemented in an interpreter.

Example: Binary Search Trees

The following is a sample Scheme implementation of a basic binary search tree’s.

;;; bst.scm

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; An implementation of a binary search tree (BST). Nodes are lists of the
;; form (value left-tree right-tree).
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; '() is the empty tree
(define is-empty? (lambda (t) (null? t)))

;; getters
(define value (lambda (node) (first node)))
(define left (lambda (node) (second node)))
(define right (lambda (node) (third node)))

;; insert value x into tree t
(define insert
    (lambda (x t)
        (if (is-empty? t)
            (list x '() '())
            (let ((V (value t))
                  (L (left t))
                  (R (right t))
                 )
                (cond
                    ((is-empty? t)
                        (list x '() '()))
                    ((= x V) ;; x already in the tree, so do nothing
                        t)
                    ((< x V)
                        (list V (insert x L) R))
                    (else
                        (list V L (insert x R)))
                )
            )
        )
    )
)

;; convert a list of things into a tree
(define make-tree
    (lambda (lst)
        (if (null? lst)
            '()
            (insert (car lst) (make-tree (cdr lst))))))


;; return a list of the values of tree in "inorder" order, i.e. for
;; a BST the list of values will be in ascending sorted order
(define list-inorder
    (lambda (t)
        (cond
            ((is-empty? t) '())
            (else (append (list-inorder (left t))
                          (list (value t))
                          (list-inorder (right t))))
        )
    )
)

(define treesort
    (lambda (lst)
        (list-inorder (make-tree lst))
    )
)

;; Returns the depth of a tree
(define depth
    (lambda (t)
        (cond
            ((is-empty? t)
                0)
            (else
                (+ 1 (max (depth (left t)) (depth (right t))))
            )
        )
    )
)

;; return the max value in a tree
(define tree-max
    (lambda (t)
        (cond
            ((is-empty? t)
                'error)
            ((is-empty? (right t))
                (value t))
            (else
                (tree-max (right t)))
        )
    )
)

;; test if x is a value in tree t
(define contains
    (lambda (x t)
        (cond
            ((is-empty? t)
                #f)
            ((= x (value t))
                #t)
            ((< x (value t))
                (contains x (left t)))
            (else
                (contains x (right t)))
        )
    )
)