Notes on Scheme Gathered from multiple sources by Lee Spector, lspector@hampshire.edu, March 1998 Translated and excerpted from an earlier version in Common Lisp, with a few Scheme-specific additions Examples early in the file were evaluated in Gambit Scheme on a Macintosh. Later examples were evaluated in MzScheme under Solaris. Symbolic Expressions (S-expressions) atoms are S-expressions lists of S-expressions are S-expressions Atoms atom turkey 1492 3turkeys u *abc$ Lists (atom) (atom turkey) (atom 1492 ocean blue) (an-atom (a list inside) and more atoms) (a list containing 5 atoms) (a list containing 8 atoms and a list (that contains 4 atoms)) (((a list containing a list containing a list of 11 atoms))) () (a list that ends with the null list ()) (not a list (not) a list )not again The Lisp evaluation rule To evaluate an atom: If it's a numeric atom, the value is the number Else it must be a variable symbol, so the value is the value of the variable To evaluate a list: evaluate the first item of the list to obtain a function, and apply that function to the values of the remaining elements of the list. Numeric S-expressions : (+ 1 1) 2 : (* 23 13) 299 : (+ 1 (* 23 13)) 300 : (+ (* 2 2) (- 10 9)) 5 : (* 2 2 2 2) 16 : (sqrt (* 2 2 2 2)) 4 : (/ 25 5) 5 : (/ 38 4) 19/2 : (sqrt -4) +2i : (* 123456789 987654321 999999999 12345678987654321) 1505341120331325629973976373036705875152651 Taking lists apart The first element of a list (be it an atom or another list) is the car of the list. the car of (alphabet soup) is alphabet the car of ((pickles beer) alphabet (soup)) is (pickles beer) the car of (((miro (braque picasso))) leger) is ((miro (braque picasso))) Everything except the first element of a list is the cdr of the list. the cdr of (alphabet soup) is (soup) the cdr of ((pickles beer) alphabet (soup)) is (alphabet (soup)) the cdr of (((miro (braque picasso))) leger) is (leger) Given a complex S-expression, you can obtain any of its component S-expressions through some combination of car and cdr. The car of the cdr of the car of ((the (nested) secret) of the (universe)) is (nested) The quote function inhibits the evaluation of its argument. It returns its argument literally. If the variable barney has the value 22, then Evaluating barney yields 22 Evaluating (quote barney) yields barney Evaluating (car (quote (betty bambam))) yields betty the single quote mark (') (itÕs actually an apostrophe) can be used as an abbreviation for (quote ...). If the variable barney has the value 22, then Evaluating barney yields 22 Evaluating 'barney yields barney Evaluating (car '(betty bambam)) yields betty cons builds lists. (cons 'woof '(bow wow)) (cons '(howl) '(at the moon)) You can create any list with cons. define can be used to assign a global value to a symbol : (define my-favorite-list '(fe fi fo fum)) : my-favorite-list (fe fi fo fum) set! can be used to change the value of a (global or local) variable: : (set! my-favorite-list '(tra la la)) : my-favorite-list (tra la la) Note that define and set! are both exceptions to the normal evaluation rule: the first argument is not evaluated. car, cdr, and cons are all non-destructive: : (define x '(the rain in spain)) : (car x) the : (cdr x) (rain in spain) : (cons 'darn x) (darn the rain in spain) : x (the rain in spain) cadr is the car of the cdr: : (cadr '(old mcdonald had a farm)) mcdonald cdar is the cdr of the car: : (cdar '((do you)(really think so?))) (you) There are more variations, at least up to 4 a's or d's: : (caddar '((deep (in the (nested) structure) lies truth))) lies The null list (cdr '(just-one-item)) '() : (cons 'naked-atom '()) (naked-atom) List and append: : (list 'it 'is 'really 'cold 'out) (it is really cold out) : (define brrrr (list 'it 'is 'really 'cold 'out)) : (list brrrr '(i am sure you will agree)) ((it is really cold out) (i am sure you will agree)) : (append brrrr '(i am sure you will agree)) (it is really cold out i am sure you will agree) : brrrr (it is really cold out) A Lisp function that is called to answer a yes-or-no question is sometimes called a predicate. Scheme predicates return #f for false and #t for true. Predicates in Lisp take various forms, but sometimes end with ? or p. : (null? 'fig-tree) #f : (null? '(bismark)) #f : (null? '()) #t : (symbol? 'eggplant) #t : (symbol? 99) #f : (symbol? '(the rain in spain)) #f : (list? 'apple) #f : (list? '(fish and telephones)) #t : (list? 33) #f : (list? '()) #t Some predicates take more than one argument: : (> 22 11) #t : (> 11 22) #f : (<= 23 23) #t : (>= 23 23) #t : (>= 100 1) #t : (>= 1 100) #f : (< 1 2 3 4 5 6 7 8 9) #t : (< 1 2 3 4 5 6 7 8 7) #f : (equal? 'foot 'foot) #t : (equal? 'nose 'ear) #f : (equal? (+ 22 33 44) (* 33 3)) #t Scheme has a variety of equality predicates, of which equal? is the most general. And, or, and not allow the combination of predicates into complex predicates. (define long-list '(1 2 3 4 can I show you out the door?)) (define lucky-number 23) : (or (equal? lucky-number (car long-list)) (equal? (* (car long-list) 2) (car (cdr long-list)))) #t : (and (equal? lucky-number (car long-list)) (equal? (* (car long-list) 2) (car (cdr long-list)))) #f : (even? lucky-number) #f : (not (even? lucky-number)) #t Cond is a powerful multiway branching construct. : (define x 23) : (cond ((equal? x 1) '(it was one)) ((equal? x 2) '(it was two)) (#t '(it was not one or two))) (it was not one or two) : (define x 2) : (cond ((equal? x 1) '(it was one)) ((equal? x 2) '(it was two)) (#t '(it was not one or two))) (it was two) IF is a special case of COND: IF testform thenform [elseform] evaluates testform. If the result is true, evaluates thenform and returns the result; if the result is null, evaluates elseform and returns the result. > (if (> 100 23) 'sure-is 'sure-aint) sure-is > (if (member 'bog '(blig blog bog bumper)) (* 99 99) (sqrt -1)) 9801 > (if (member 'fog '(blig blog bog bumper)) (* 99 99) (sqrt -1)) 0+1i Note that the thenform and the elseform are both restricted to being single forms. In contrast, you can specify any number of forms in a cond clause: > (define temperature 8) > (cond ((< temperature 32) (display '(yowch -- it is cold!)) (newline) (display '(i will play god and change that!)) (newline) (set! temperature 78)) (#t (display '(well i guess it is not so bad)) (newline) (display '(where do you think we are? hawaii?)) (newline))) (well i guess it is not so bad) (where do you think we are? hawaii?) If you want multiple forms in an IF you can use a BEGIN: > (if (< temperature 32) (begin (display '(yowch -- it is cold!)) (newline) (display '(i will play god and change that!)) (newline) (set! temperature 78)) (begin (display '(well i guess it is not so bad)) (newline) (display '(where do you think we are? hawaii?)) (newline))) (well i guess it is not so bad) (where do you think we are? hawaii?) You get errors if you try to take the car or cdr of a non-nil atom : (car 'foo) *** ERROR -- PAIR expected (car 'foo) 1: : (cdr 'foo) *** ERROR -- PAIR expected (cdr 'foo) 1: You get "funny dots" if you try to cons things onto non-lists : (cons 'a 'b) (a . b) Although "dotted pairs" have their uses, we will ignore them for now. That means that you're probably doing something wrong if you get dots in your answers. Scheme comments: (car '(a b c)) ; this is a comment Scheme procedures (aka functions) Here's a constant function that takes no arguments : (define dumb-function (lambda () '(you might as well use a variable for something like this))) dumb-function : (dumb-function) (you might as well use a variable for something like this) Here's a function that takes one argument and returns the argument plus 20: : (define add-20 (lambda (n) (+ n 20))) add-20 : (add-20 15) 35 Here's a function that takes two arguments and returns their sum: : (define my-sum (lambda (n1 n2) (+ n1 n2))) my-sum : (my-sum 99 100) 199 Here's a version of + that also sets the global variable *last-sum*: : (define +store (lambda (n1 n2) (set! *last-sum* (+ n1 n2)) *last-sum*)) +store : (+store 99 100) 199 : *last-sum* 199 Here's a function that takes 3 arguments and returns a descriptive list: : (define funny-arg-lister (lambda (arg1 arg2 arg3) (cons (cons 'arg1 (cons arg1 '())) (cons (cons 'arg2 (cons arg2 '())) (cons (cons 'arg3 (cons arg3 '())) '()))))) funny-arg-lister : (funny-arg-lister 'a 'b '(x y z)) ((arg1 a) (arg2 b) (arg3 (x y z))) Here's a cleaner version of funny-arg-lister using the LIST function: : (define funny-arg-lister (lambda (arg1 arg2 arg3) (list (list 'arg1 arg1) (list 'arg2 arg2) (list 'arg3 arg3)))) funny-arg-lister : (funny-arg-lister 'a 'b '(x y z)) ((arg1 a) (arg2 b) (arg3 (x y z))) Here's another simple function : (define double-cons (lambda (one-thing another-thing) (cons one-thing (cons one-thing another-thing)))) double-cons : (double-cons 'hip '(hooray)) (hip hip hooray) Function arguments are LOCAL variables : (define shoes 'nikes) : shoes nikes : (define run (lambda (shoes) (display (append '(i think i will take a trot in my) (list shoes))) (newline) (set! shoes (append '(old beat-up) (list shoes))) (display (append '(i think i will take a trot in my) shoes)) (newline) 'ran)) run : (run shoes) (i think i will take a trot in my nikes) (i think i will take a trot in my old beat-up nikes) ran : shoes nikes The length function returns the number of elements in a list : (length '(a b c d e)) 5 : (length '(the (deeper (you (go (the (nuller (you (get))))))))) 2 Here's a function to access an element of a list by number: (define nth (lambda (n list) (if (equal? n 0) (car list) (nth (- n 1) (cdr list))))) > (nth 3 '(a b c d e f g)) d Here's a function to return a random element of a list (define random-elt (lambda (choices) (nth (random (length choices)) choices))) > (random-elt '(fish cheese giraffe macaroni radio)) macaroni > (random-elt '(fish cheese giraffe macaroni radio)) radio This returns a random element as a singleton list (define one-of (lambda (set) (list (random-elt set)))) > (one-of '(how are you doing today?)) (you) The following was adapted from Norvig's Paradigms of AI Programming, Chapter 2 A Grammar for a (miniscule) Subset of English: Sentence => Noun-Phrase + Verb-Phrase Noun-Phrase => Article + Noun Verb-Phrase => Verb + Noun-Phrase Article => the, a, ... Noun => man, ball, woman, table ... Verb => hit, took, saw, liked ... The grammar can be used to generate sentences: To get a Sentence, append a Noun-Phrase and a Verb-Phrase To get a Noun-Phrase, append an Article and a Noun Choose "the" for the Article Choose "man" for the Noun The resulting Noun-Phrase is "the man" To get a Verb-Phrase, append a Verb and a Noun-Phrase Choose "hit" for the Verb [etc.] The grammar can be used as the basis of the following Scheme functions: (define sentence (lambda () (append (noun-phrase) (verb-phrase)))) (define noun-phrase (lambda () (append (article) (noun)))) (define verb-phrase (lambda () (append (verb) (noun-phrase)))) (define article (lambda () (one-of '(the a)))) (define noun (lambda () (one-of '(man ball woman table)))) (define verb (lambda () (one-of '(hit took saw liked)))) > (sentence) (the table took the table) > (sentence) (a woman liked a ball) > (sentence) (the table liked a woman) > (sentence) (the woman hit a woman) > (noun-phrase) (the table) > (verb-phrase) (took a table) The following is necessary in MzScheme to get access to the trace procedure: > (require-library "trace.ss") > (trace sentence) (sentence) > (sentence) |(sentence) |(the table hit a ball) (the table hit a ball) > (trace noun-phrase verb-phrase article noun verb) (noun-phrase verb-phrase article noun verb) > (sentence) |(sentence) | (noun-phrase) | |(article) | |(the) | |(noun) | |(woman) | (the woman) | (verb-phrase) | |(verb) | |(took) | |(noun-phrase) | | (article) | | (a) | | (noun) | | (man) | |(a man) | (took a man) |(the woman took a man) (the woman took a man) > > (untrace) () Here's a function using COND that returns an atom indicating whether its argument is even or odd (or not a number). (define even-or-odd? (lambda (n) (cond ((not (integer? n)) 'neither) ((even? n) 'even) ((odd? n) 'odd)))) > (even-or-odd? 24) even > (even-or-odd? 25) odd > (even-or-odd? '(swahili)) neither The following member? function checks to see if its first argument occurs in its second: (define member? (lambda (item list) (if (null? list) #f (or (equal? (car list) item) (member? item (cdr list)))))) (define five-colleges '(amherst umass hampshire smith mount-holyoke)) > (member? 'hampshire five-colleges) #t > (member? 'oberlin five-colleges) #f Scheme includes a built-in function called member that does a similar thing. Note, however, that it returns the matching cdr rather than #t: > (member 'hampshire five-colleges) (hampshire smith mount-holyoke) > (member 'oberlin five-colleges) #f The LET special form is used to create local variables. > (let ((foo 2) (bar 3)) (+ foo bar)) 5 LET ({variable | (variable value) }*) {form}* creates a binding for each variable (in parallel) and evaluates forms in the resulting environment. Returns the value of the last form. > (define laadeedaa 'hihohiho) > (let ((laadeedaa 'feefifofum)) (list laadeedaa laadeedaa laadeedaa)) (feefifofum feefifofum feefifofum) > laadeedaa hihohiho Note that the initializations in a LET are performed "in parallel" -- you may not rely on the earlier ones being performed before the later ones. If you want sequential initialization behavior, use LET*: > (let* ((foo 2) (bar (+ foo 3))) (+ foo bar)) 7 Recursion and control structures A closer look at recursive programming: Wilensky p. 89: ...we have the collowing components to recursive programming: (1) Breaking down the task at hand into a form that involves simpler versions of the same task. (2) Specifying a way to combine the simpler versions of the task to solve the original problem. (3) Identifying the "grounding" situations in which the task can be accomplished directly. (4) Specifying checks for these grounding cases that will be examined before recursive steps are taken. (define factorial (lambda (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))) > (factorial 5) 120 > (factorial 100) 93326215443944152681699238856266700490715968264381621468592963895217599 993229915 6089414639761565182862536979208272237582511852109168640000000 00000000000000000 > (require-library "trace.ss") > (trace factorial) (factorial) > (factorial 5) |(factorial 5) | (factorial 4) | |(factorial 3) | | (factorial 2) | | |(factorial 1) | | |1 | | 2 | |6 | 24 |120 120 ;; here's a recursive length function > (define my-length (lambda (list) (if (null? list) 0 (+ 1 (my-length (cdr list)))))) > (my-length '(let it snow let it snow let it AAAARRRRRGGGGGHHHH!!!!)) 9 Sometimes iteration seems more natural. DO is an general, powerful tool for iteration DO ({var | (var [init [step]])}*) (end-test {result}*) {statement}* at the beginning of each iteration, evaluates all the init forms (before any var is bound), then binds each var to the value of its init . Then evaluates end-test; if the result is #f, execution proceeds with the body of the form. If the result is not #f, the result forms are evaluated and the value of the last result form is returned. At the beginning of the second and subsequent iterations, all step forms are evaluated, then all variables are updated. > (do ((i 1 (+ i 1))) ((> i 10) 'got-to-ten) (display i) (newline)) 1 2 3 4 5 6 7 8 9 10 got-to-ten Scheme provides an additional iteration mechanism via the map function. procedure: map proc list1 list2 ... The lists must be lists, and proc must be a procedure taking as many arguments as there are lists. If more than one list is given, then they must all be the same length. Map applies proc element-wise to the elements of the lists and returns a list of the results, in order from left to right. The dynamic order in which proc is applied to the elements of the lists is unspecified. (define smell-test (lambda (thing) (cond ((member thing '(rose daisy chocolate)) 'good) ((member thing '(dirty-socks dirty-politics toe-cheese)) 'lousy) (#t 'bad)))) > (map smell-test '(rose roast cheese toe-cheese chocolate)) (good bad bad lousy good) Functions as first-class objects Higher order functions (define double (lambda (x) (list x x))) > (double 3) (3 3) > (apply double '(3)) (3 3) (define list-o-numbers '(22 44 66 88)) > (apply + list-o-numbers) 220 (define my-function double) > (my-function 3) (3 3) (define x '(a b c d e f g h i j k l m n o p q r)) > (map double x) ((a a) (b b) (c c) (d d) (e e) (f f) (g g) (h h) (i i) (j j) (k k) (l l) (m m) (n n) (o o) (p p) (q q) (r r)) > (map + '(1 2 3) '(8 9 10)) (9 11 13) (mapcar #'+ '(1 2 3) '(8 9)) (setq x '( 1 2 3)) (apply #'+ x) (+ 1 2 3) You can always use a lambda expression instead of a named function > ((lambda (x) (+ x x)) 23) 46 > ((lambda (a b) (list a a b 'and 'furthermore b)) 'spam 'clam) (spam spam clam and furthermore clam) Lambda expressions are particularly handy in conjunction with map > (map (lambda (x) (+ x 100)) '(2 4 6 8 10)) (102 104 106 108 110) Garbage collection in MzScheme: Ê (collect-garbage) forces an immediate garbage collection. Since MzScheme uses a ``conservative'' garbage collector, some effectively unreachable data may remain uncollected (because the collector cannot prove that it is unreachable). This procedure provides some control over the timing of collections. Garbage will obviously be collected even if this procedure is never called. Ê (dump-memory-stats) dumps information about memory usage to the (low-level) standard output port. {grendel} % mzscheme Welcome to MzScheme version 50, Copyright (c) 1995-97 PLT (Matthew Flatt) > (dump-memory-stats) Begin Dump ***Static roots: From 0x74000 to 0x7cf98 From 0x85038 to 0x86508 Total size: 42088 ***Heap sections: Total heap size: 475136 Section 0 from 0x9a000 to 0xaa000 0/16 blacklisted Section 1 from 0xbc000 to 0xcc000 0/16 blacklisted Section 2 from 0xcd000 to 0xe6000 2/25 blacklisted Section 3 from 0xf0000 to 0x100000 1/16 blacklisted Section 4 from 0x10c000 to 0x11f000 1/19 blacklisted Section 5 from 0x131000 to 0x149000 1/24 blacklisted ***Free blocks: 0x138000 size 69632 not black listed Total of 69632 bytes on free list ***Blocks in use: (kind(0=ptrfree,1=normal,2=unc.,3=stubborn):size_in_bytes, #_marks_set) (3:4,0)(3:24,0)(3:16,0)(3:48,0)(0:1360,0)(0:512,0)(0:312,0)(1:128,0)(0:1024,0)() blocks = 70, bytes = 405504 End Dump > (define n-foos (lambda (n) (if (equal? n 0) '() (cons 'foo (n-foos (- n 1)))))) > (define a-lot-of-foos (n-foos 2000)) > (dump-memory-stats) Begin Dump ***Static roots: From 0x74000 to 0x7cf98 From 0x85038 to 0x86508 Total size: 42088 ***Heap sections: Total heap size: 475136 Section 0 from 0x9a000 to 0xaa000 0/16 blacklisted Section 1 from 0xbc000 to 0xcc000 0/16 blacklisted Section 2 from 0xcd000 to 0xe6000 2/25 blacklisted Section 3 from 0xf0000 to 0x100000 1/16 blacklisted Section 4 from 0x10c000 to 0x11f000 1/19 blacklisted Section 5 from 0x131000 to 0x149000 1/24 blacklisted ***Free blocks: 0x140000 size 4096 start black listed 0x148000 size 4096 not black listed Total of 8192 bytes on free list ***Blocks in use: (kind(0=ptrfree,1=normal,2=unc.,3=stubborn):size_in_bytes, #_marks_set) (1:16,0)(1:16,0)(1:16,0)(1:16,0)(1:16,0)(1:16,0)(1:16,0)(1:16,0)(1:20000,0)(2:1) blocks = 81, bytes = 466944 End Dump > (collect-garbage) > (dump-memory-stats) Begin Dump ***Static roots: From 0x74000 to 0x7cf98 From 0x85038 to 0x86508 Total size: 42088 ***Heap sections: Total heap size: 475136 Section 0 from 0x9a000 to 0xaa000 0/16 blacklisted Section 1 from 0xbc000 to 0xcc000 0/16 blacklisted Section 2 from 0xcd000 to 0xe6000 2/25 blacklisted Section 3 from 0xf0000 to 0x100000 1/16 blacklisted Section 4 from 0x10c000 to 0x11f000 1/19 blacklisted Section 5 from 0x131000 to 0x149000 1/24 blacklisted ***Free blocks: 0xfb000 size 4096 not black listed 0xff000 size 4096 not black listed 0x10c000 size 4096 not black listed 0x111000 size 4096 not black listed 0x116000 size 8192 not black listed 0x119000 size 4096 not black listed 0x11d000 size 4096 not black listed 0x132000 size 8192 not black listed 0x137000 size 4096 not black listed 0x13a000 size 20480 not black listed 0x140000 size 4096 start black listed 0x148000 size 4096 not black listed Total of 73728 bytes on free list ***Blocks in use: (kind(0=ptrfree,1=normal,2=unc.,3=stubborn):size_in_bytes, #_marks_set) (1:16,12)(1:16,256)(1:16,256)(1:16,256)(1:16,256)(1:16,256)(1:16,256)(1:16,256)) blocks = 69, bytes = 401408 End Dump > (set! a-lot-of-foos '()) > (collect-garbage) > (dump-memory-stats) Begin Dump ***Static roots: From 0x74000 to 0x7cf98 From 0x85038 to 0x86508 Total size: 42088 ***Heap sections: Total heap size: 475136 Section 0 from 0x9a000 to 0xaa000 0/16 blacklisted Section 1 from 0xbc000 to 0xcc000 0/16 blacklisted Section 2 from 0xcd000 to 0xe6000 2/25 blacklisted Section 3 from 0xf0000 to 0x100000 0/16 blacklisted Section 4 from 0x10c000 to 0x11f000 0/19 blacklisted Section 5 from 0x131000 to 0x149000 1/24 blacklisted ***Free blocks: 0xfb000 size 4096 not black listed 0xff000 size 4096 not black listed 0x10c000 size 4096 not black listed 0x111000 size 4096 not black listed 0x116000 size 8192 not black listed 0x119000 size 4096 not black listed 0x11d000 size 4096 not black listed 0x132000 size 8192 not black listed 0x137000 size 4096 not black listed 0x13a000 size 61440 not black listed Total of 106496 bytes on free list ***Blocks in use: (kind(0=ptrfree,1=normal,2=unc.,3=stubborn):size_in_bytes, #_marks_set) (2:16,769)(1:16,1)(3:24,20)(3:16,137)(3:48,1)(0:312,1)(1:128,1)(1:96,1)(3:16,22) blocks = 61, bytes = 368640 End Dump > Lazy evaluation > (define all-powers-of-2 (lambda (initial-exponent) (cons (expt 2 initial-exponent) (delay (all-powers-of-2 (+ initial-exponent 1)))))) > (define nth-force ;; for lists in which the cdr may be a promise (lambda (n list) (if (equal? n 0) (car list) (nth-force (- n 1) (force (cdr list)))))) > (all-powers-of-2 0) (1 . #) > (nth-force 10 (all-powers-of-2 0)) 1024