0. Table of Contents 1. Working With DrScheme 2. Primitive Data Types 3. List Processing 4. Memory Modification 5. Comparing and Testing 6. Boolean Expressions 7. User Input and Output 8. Conditional Statements 9. Iteration and Recursion 10. Global and Local Variables 11. Functional Programming 12. Alternative Iteration 13. Files and Directories 14. Reading and Writing Files 1. Working With DrScheme We'll be using the DrScheme programming environment in class and we recommend that for your personal computer you download the latest version appropriate for your operating system from the DrScheme web site (http://www.drscheme.org/). Unless indicated otherwise make sure that your DrScheme 'Language' is set to 'Pretty Big'. To do so click on the 'Language' menu in DrScheme and you'll find 'Pretty Big' under 'Professional Languages' (you'll have to click on the 'PLT' tab to show the 'PLT' family of languages). In 'Pretty Big' you'll find that 'first' and 'rest' are not defined. So, either use 'car' and 'cdr' or define 'first' and 'rest'. To do the latter, just include the following two definitions in your code. (define first car) (define rest cdr) The presentation in this missive assumes that you've read Chapter 5: 'Computational Muddles' of 'Talking With Computers'. 2. Primitive Data Types Symbols: any sequence of characters including at least one character other than a digit. There are some exceptions: you can't include the single quote, double quote, back quote or any parentheses {},[],(). In addition, the first character of a symbol can't be a sharp sign #. To define a symbol 'name' to have the value of some 'expression', type (define name expression). Try variations on the following: > (define foo 1) > foo 1 > (define foo_bar 2) > foo_bar 2 > (define 1_foo 3) > 1_foo 3 > (define n (+ 1 2 3)) > n 6 If you type a defined symbol to Scheme, it will respond by printing the 'value' of the symbol. If you type a symbol that hasn't been defined, Scheme will complain: > bar reference to undefined identifier: bar So far we've been defining symbols to have values corresponding to integers. There are other sorts of numbers available in Scheme including rationals, real numbers in decimal notation and various kinds of floating point numbers: > (define third 1/3) > third 1/3 Another primitive data type is the string; in Scheme, strings are always enclosed in double quotes: > (define my_name "Tom Dean") > my_name "Tom Dean" Unlike symbols that have values assigned to them by 'define'. Strings and numbers always evaluate to themselves: > "Vanessia Wu" "Vanessia Wu" > 2.712 2.712 The single quote is shorthand for the 'quote' function. The quote function suppresses evaluation. The following two invocations are equivalent: > 'foo foo > (quote foo) foo Note that by quoting a symbol you can refer to the symbol rather than any value that it might have; you can also refer to a symbol that doesn't have a defined value. Every symbol has a name but not all symbols have values. 3. List Processing One of the most important data types in Scheme is the list; lists are used to store information, create more complex data structures and even represent programs. The 'list' function takes any number of arguments and constructs a list whose elements correspond to the values of its arguments: > (list 1 2 3) (1 2 3) > (define ones (list "one" 1 'one )) > ones ("one" 1 one) > (define twos (list (+ 1 1) (/ 6 3) (/ (* 2 3) 2))) > twos (2 2 2) Reflect on the last two definitions; the 'list' function evaluates its arguments (arguments are like the words to the right of a shell command). The strings and the numbers evaluated to themselves and we used to the quote to suppress the evaluation of the symbol 'one'. You can 'nest' lists simply by calling the 'list' function as one of the arguments to 'list': > (define nest (list 1 2 (list 3 4) 5 6)) > nest (1 2 (3 4) 5 6) You can also create lists using quote; in this case, the resulting list is specified literally and no evaluation takes place: > (define nums '(1 2 3 4)) Notice what happens when we incorrectly try to create a nested list inside a list specified with quote: > (define nest '(1 2 (list 3 4) 5 6)) > nest (1 2 (list 3 4) 5 6) To achieve the intended effect we just specify what we want literally: > (define nest '(1 2 (3 4) 5 6)) > nest (1 2 (3 4) 5 6) It doesn't do much good to create lists if you can't subsequently take them apart and access their contents. The most basic functions for accessing lists are called 'car' and 'cdr'. These names refer to locations called 'registers' in the memory of a now defunct computer. You can use these name if you like but you can also give them more mnemonic names. The 'car' of a list is the first element of the list and the 'cdr' of a list is the list consisting of all the rest of the elements of the list but the first one. As mentioned in class, often programmers define 'first' and 'rest' as aliases for 'car' and 'cdr': > (define first car) > (define rest cdr) What do think the value of 'car' is? > car #<primitive:car> > cdr #<primitive:cdr> This indicates that 'car' and 'cdr' are defined as primitive functions. And now we've defined 'first' and 'rest' to have the same definitions as 'car' and 'cdr' respectively. > first #<primitive:car> > rest #<primitive:cdr> Now we can use these functions to access the contents of lists: > (define nums '(1 2 (3 4) 5 6)) > nums (1 2 (3 4) 5 6) > (first nums) 1 > (rest nums) (2 (3 4) 5 6) We can use various combinations of 'first' and 'rest' to access any element or sublist of given list. For example, suppose that you want to grab the 4 in '(0 (1 (3 4)) 5). First we'll do it in several steps: > (rest '(0 (1 (3 4)) 5)) <<< step 1 ((1 (3 4)) 5) > (first '((1 (3 4)) 5)) <<< step 2 (1 (3 4)) > (rest '(1 (3 4))) <<< step 3 ((3 4)) > (first '((3 4))) <<< step 4 (3 4) > (rest '(3 4)) <<< step 5 (4) > (first '(4)) <<< step 6 4 Now we put this all together and apply these steps in the order of evaluation which actually looks as if we're doing it backwards or at least inside out, but you should convince yourself that this is actually in the appropriate evaluation order. step 6 step 5 step 4 step 3 step 2 step 1 > (first (rest (first (rest (first (rest '(0 (1 (3 4)) 5))))))) 4 There's also a more primitive list constructor called 'cons' that constructs lists in exactly the same way that 'car' ('first') and 'cdr' ('rest') take them apart. > (define lst (cons 0 '(1 2 3))) > lst (0 1 2 3) > (car lst) 1 > (cdr lst) (1 2 3) > (cons 0 (cons 1 (cons 2 (cons 3 ())))) (0 1 2 3) The 'assoc' function allows us to use so-called association lists. An association list is a list of pairs each pair consisting of a key and an item associated with that key. For example, the following association list maps the names of fields to their offsets in a list representing a database record. > (define employee_fields '((id 1) (first 2) (last 3) (address 4))) We can use 'assoc' to find the offset for a field by specifying the field name as the key. > (assoc 'last employee_fields) (last 3) > (car (cdr (assoc 'last employee_fields))) 3 Note that if the key cannot be found, 'assoc' returns false #f. > (assoc 'middle employee_fields) #f For this reason, you may want to implement your field offset lookup as: > (define (offset field table) (if (assoc field table) (car (cdr (assoc field table))) (printf "offset: unknown field ~a~%" field))) See Section 7 for a discussion of 'printf'. 4. Memory Modification The 'define' command is used to define the value of symbols. When you want to redefine a symbol you can use 'define' to do so, but often once the symbol is initialized to a particular value subsequent changes are typically made using another command called 'set!' The '!' is often added to commands that are said to 'destructively modify' their arguments. The 'set!' command is like 'set' in the C-shell except instead of writing 'set var = expression' you write '(set! var expression)'. For our purposes, the first argument to 'set!' is expected to be a symbol but, unlike symbols appearing as arguments to other functions, this first argument is not evaluated; 'set!' works like 'define' does in setting a symbol to have a particular value. > (define counter 0) > counter 0 > (set! counter (+ counter 1)) > counter 1 > (set! counter (+ counter 1)) > counter 2 There are also commands that allow you destructively alter the structure of lists. > (define nums (list 1 2 3)) > (set-car! nums 7) > nums (7 2 3) > (set-cdr! (cdr (cdr nums)) (list 4 5 6)) > nums (7 2 3 4 5 6) 5. Comparing and Testing Each of the primitive data types has a corresponding predicate for testing membership. A predicate is just a function that returns true or false. In scheme, '#f' means 'false' and '#t' means 'true'. The '?' is often tacked on the end of predicates to identify them as such, but the '?' is just a convention - it's not required when defining your own predicates. > (number? 1) #t > (string? "string") #t > (list? '(1 2 3)) #t > (list? "string") #f > (symbol? 'a) #t The 'string<?' predicate is a single symbol consisting of eight characters; we might have called it 'string-less-than?' instead of 'string<?' 'string<?' is the string analog of '<' for numbers. You can also check for special cases: > (zero? 0) #t > (null? '()) #t There are also variants of equality and inequality for every primitive type (substitute '>', '=', '<=', '>=' for '<' in the following to get the other variants). > (< 1 2) #t > (string<? "a" "b") #t > The '<' predcate is an example of a predicate that doesn't have the '?' tacked on the end. Note that 'string=?', 'string<?', 'string>?', 'string<=?' and 'string>=?' all use lexicographic order for making comparisons whereas '=', '<', '>', '<=', '>=' use numerical order. In Scheme '=' means numerical equality in contrast to the C-shell where '=' is used for assignment (in 'set' and '@'). There is also an equality predicate defined on more general types (note that for symbols, lowercase/uppercase doesn't matter - case does matter for strings however - Scheme can be configured so that case does matter but the default is for it to be insensitive to case): > (equal? 'a 'A) #t > (equal? 'a "a") #f > (equal? 1 (first (rest '(0 1 2 3)))) #t > (equal? "a" "A") #f > (equal? 1 (- 2 1)) #t 6. Boolean Expressions We can combine predicate invocations with the boolean operators: 'and', 'or', 'not'. The first, the so-called conjunctive operator 'and' takes any number of arguments including zero it returns true #t if all of its arguments evaluate to #t and false #f otherwise: > (and #t #t) #t > (and #t #t #f) #f > (and) #t We typically use boolean operators with predicates, e.g., > (and (list? arg) (not (null? (rest arg))) (number? (first arg))) returns #t if arg is a list, has at least one element, and its first element is a number. The disjunctive operator 'or' works similarly; it returns #t if at least one of its arguments evaluates to #t and false #f otherwise: > (or #f #t #t) #t > (or #f) #f > (or) #f Again, the arguments typically involve predicates or nested boolean expressions, e.g., > (or (and (number? arg) (> arg 0)) (symbol? arg)) returns #t if arg is a number greater than zero or a symbol. See if you can explain why '(or) => #f' and '(and) => #t'. The negation operator 'not' accepts exactly one argument and returns #t if its only argument evaluates to #f and returns #f if its only argument evaluates to #t. > (not #t) #f > (not #f) #t > (not (and (number? 1) (or (string? "") (null? '(1 2 3))))) #f Note that functions defined on strings, numbers, non-empty lists, etc., will complain if you call them with the wrong type of inputs: > (rest '()) cdr: expects argument of type <pair>; given () > (string=? 1 "one") string=?: expects type <string> as 1st argument, given: 1; other arguments were: "one" For this reason, it is often important to check the type of arguments before evaluating them. Note that in evaluating an 'and', the Scheme interpreter won't evaluate the second conjunct if the first conjunct returns #f; we can use this fact to check arguments and avoid errors of the sort illustrated above. > (and (not (null? lst)) (not (null? (rest lst))) (= 1 (first (rest lst)))) #f Here's a predicate 'my-special-list-type?' that returns #t for the following type of data structure: (list symbol number (list)) and #f otherwise; so, for example: > (my-special-list-type? '(a 1 ())) #t > (my-special-list-type? (list 'foo 1.2 (list))) #t > (my-special-list-type? (list 'foo 1.2)) #f > (my-special-list-type? 3.14) #f > (my-special-list-type? (list 'foo 1.2)) #f Here's the definition of 'my-special-list-type?': (define (my-special-list-type? arg) (and (list? arg) (not (null? arg)) (symbol? (first arg)) (not (null? (rest arg))) (number? (first (rest arg))) (not (null? (rest (rest arg)))) (null? (first (rest (rest arg)))))) Note that the first two conjuncts (list? arg) and (not (null? arg)) have to be there to make sure that the third conjunct (symbol? (first arg)) won't cause an error on an input like '(). The fourth conjunct (not (null? (rest arg))) has to be there to make sure that the fifth conjunct (number? (first (rest arg))) won't cause an error on an input like '(1). Similarly, the sixth conjunct prevents the seventh conjunct from causing an error. 7. User Input and Output The 'print', 'display' and 'newline' commands are the simplest expedient for printing messages to the standard output. 'newline' just outputs a newline character. 'print' and 'display' are similar though they treat some arguments slightly different, e.g., strings: > (display "foo") foo> (print "foo") "foo"> The Scheme 'printf' behaves pretty much like the C-shell 'printf' except that we use '~%' for '\n' and directives like '~a' and '~v' to interpolate variables into the format string, so that instead of % printf "the $last number is $num\n" you would write > (printf "the ~a number is ~a~%" last num) For each appearance of '~a' in the format string there must be one additional argument to 'printf'. > (define one 1) > (define two 2) > (define three 3) > (printf "~a is less than ~a is less than ~a~%" one two three) 1 is less than 2 is less than 3 > (printf "~a is less than ~a~%" one) printf: format string requires 2 arguments, given 1; arguments were: "~a is less than ~a~%" 1 The '~a' directive causes its corresponding argument to be printed using 'display' while the '-v' directive causes its corresponding argument to be printed using 'print'. You can learn about other directives using the DrScheme Help Desk (just type 'printf'). The 'read' function allows the user to type in an expression, a number, string, symbol or list. > (define (weight) (printf "Please enter your weight in pounds: ") (printf "You don't weigh ~a kilograms!~%" (/ (read) 2.2))) > (weight) Please enter your weight in pounds: 145 You don't weigh 65.9090909090909 kilograms! You can also use 'read' and 'print' to read from and write to files by using 'ports'; see 'open-input-file' and 'open-output-file' to create ports and close-input-port and 'close-output-port to close them when you're done. 8. Conditional Statements Scheme conditionals are simpler and easier to use than conditionals in the C-shell. The 'else' clause is optional; however, if the condition of a conditional statement lacking an 'else' clause evaluates to '#f' then the result of the statement is undefined. > (if (symbol? 'a) 1 2) 1 > (if (null? '(1 2)) 1) > (if (and (number? 1) (string? "a")) 1 2) 1 There is also a generalized conditional statement 'cond' which works like nested if-then-else statements and is very convenient for all sorts of complicated tests. Here's the general format: (cond ( test_1 expression_1 ) ( test_2 expression_2 ) ( test_3 expression_3 ) ( test_4 expression_4 ) ( else expression_5 )) => (if test_1 expression_1 (if test_2 expression_2 (if test_3 expression_3 (if test_4 expression_4 (if #t expression_5 ))))) Each ( test_n expression_n ) is referred to as a 'clause'. Note that the first test to evaluate to #t will cause its corresponding expression to be evaluated. The keyword 'else' evaluates to '#t' in the context of a conditional statement. It should only be used as the test for the last clause - usually referred to as the 'default' clause. If no test evaluates to #t then the result of the 'cond' statement is undefined. > (define exp 1.2) > (cond ((list? exp) (printf "It's a list!~%")) ((symbol? exp) (printf "It's a symbol!~%")) ((number? exp) (printf "It's a number!~%")) (else (printf "I haven't the foggiest!~%"))) It's a number! 9. Iteration and Recursion Recursion is a way of thinking about iteration and a method for implementing iterative algorithms. Recursive procedures can be and routinely are written in almost any modern programming language including C, Java, Perl Python, etc. For this reason, recursion doesn't really belong as a topic in the manual for a programming language. To understand recursion you have to practice solving problems with recursion. I suggest that you reread Section 4.1 in 'Don't Sweat the Syntax' on how to translate specifications into implementations. You might also want to look at 'Recursive Definitions' on the web page at http://www.cs.brown.edu/~tld/talk/home-Z-H-6.html#node_sec_4.2.3. And work through the examples and exercises in 'Calling and Creating Functions' ../04/10/29/exercises.txt and the section entitled 'Iteration and Recursion' in ../04/11/01/exercises.txt. 10. Global and Local Variables When you define a variable at the top level (i.e., not inside the body of a function), that variable is said to have 'global scope'; you can reference it anywhere including inside the body of other functions as long as they don't have a formal parameter of the same name, e.g., > (define num 2.2) > (define (scale base) (* num base)) > (define (square num) (* num num)) The function 'scale' has access to the global variable 'num' while 'square' has a formal parameter of the same name that masks the global 'num'. Within the 'square' function 'num' refers to the local variable introduced as its single formal parameter. There are times however when you want to introduce a 'local' variable (besides a formal parameter) to keep track of some information in the midst of carrying out a computation. This is easily accomplished by introducing variables by way of 'let'. > (define num 1) > (printf "num = ~a~%" num)) num = 1 > (let ((num 0)) (printf "num = ~a~%" num) (set! num 17) (printf "num = ~a~%" num)) num = 0 num = 17 > (printf "num = ~a~%" num)) num = 1 Inside the body of the 'let' the local variable 'num' masks the 'num' introduced by the 'define'. Moreover, changes to the local 'num' do not affect the global value of 'num'. The scope of 'num' is said to be 'lexical' or 'static' in the sense that the scope of the local 'num' is restricted to the code within body of the 'let', i.e., (let ((num 0)) ... body ... ); what happens outside of the body of the let doesn't affect the local value of 'num'. If we call a function within the body of the 'let', the definition of that function is outside the scope of the 'let' and, unless it has a formal parameter with the same name or introduces a local value with the same name using 'let', changes made to the variable in the body of the function will be reflected in the global variable: > (define num 1) > (printf "num = ~a~%" num) num = 1 > (define (test arg) (set! num arg) > (define (test arg) (set! num arg)) > (let ((num 0)) (test 43) (printf "num = ~a~%" num) (set! num 17) (printf "num = ~a~%" num)) num = 0 num = 17 > (printf "num = ~a~%" num) num = 43 Here's an example of a procedure that is difficult to write without the use of a local variable introduced using 'let'. > (define pair (list 1 2)) > (define (swap pair) (let ((tmp (car pair))) (set-car! pair (car (cdr pair))) (set-car! (cdr pair) tmp))) > pair (1 2) > (swap pair) > pair (2 1) 11. Functional Programming Functional programming is a style of programming that often makes for very simple (and elegant) solutions to complicated problems. Suppose that you have three columns of numbers > (define uno '(1 2 3)) > (define dos '(4 5 6)) > (define tres '(7 8 9)) and you want to add the rows, i.e., the sum of the first number in each column, the sum of the second number in each column, etc. Here's how you do this using a 'map' operator: > (map + uno dos tres) (12 15 18) The 'map' operator takes a function (in this case '+') and one or more lists that will serve as the arguments to the function. 'map' takes each 'cross section' of the lists, e.g., the first cross section in the example above would consist of the numbers 1, 4 and 7, and then applies the function to each cross section; it then returns a list of the results. This next example turns rows into columns: > (map list uno dos tres) ((1 4 7) (2 5 8) (3 6 9)) If we don't have a function that does exactly what we want we can define an anonymous function using 'lambda'. When you specify a function definition using 'define' the next two expressions produce identical results: (define (function_name arg_1 arg_2 ... arg_n) body ) (define function_name (lambda (arg_1 arg_2 ... arg_n) body )) However, in some cases, you want to define a special-purpose function that will only be used for in one place in your program. Defining single-use functions is useful in functional programming. Here's how you would cube a list of numbers using 'map' and 'lambda': > (map (lambda (x) (* x x x)) '(1 2 3 4 5 6)) (1 8 27 64 125 216) The 'apply' function takes a function and a list of items that can serve as arguments to the function: > (apply + '(1 2 3 4)) 10 Suppose you have a list of numbers corresponding to the prices of the parts for a product you're thinking of manufacturing and you want to know the percentage that each part contributes to the total cost (this is also referred to as normalizing a set of numbers): > (map (lambda (cost) (/ cost 1.0 (apply + costs))) costs) (0.14 0.19 0.41 0.26) 12. Alternative Iteration I certainly don't want to force you to implement iterative programs using recursion if you find it difficult or somehow 'unnatural'. Some people find recursion more intuitive than 'foreach' or 'while' loops. Perhaps it is just a matter of taste. Scheme allows a variety of iterative techniques besides recursion or the sort of iteration implicit in the mapping functions common in functional programming. One such very general iterative construct is the 'do' loop whose general form is: (do (variable-initial-step-triples) (loop-exit-condition terminal-expressions) body ) where variable-initial-step-triples take the form ( variable initial-value step-value ) the loop-exit-condition is a test which is evaluated on each iteration such that if the test returns true (#t) then the loop is terminated and the terminal-expressions are evaluated; the loop returns the value of the last terminal expression. It's easier to understand if you just look at a simple example. Here's an iterative version of factorial: > (define (factorial n) (do ((i n (- i 1)) (r 1 (* r i))) ((= i 1) r))) > (factorial 4) 24 Note that 'factorial' has an empty 'body'. The next example - a test for primality - has one expression in its body: > (define (prime? n) (do ((factor (if (even? n) (/ n 2) (/ (- n 1) 2)) (- factor 1)) (flag #t)) ((or (= factor 1) (not flag)) flag) (if (= 0 (remainder n factor)) (set! flag #f)))) > (prime 18) #f > (prime 37) #t If you're really pining for your old 'foreach' and 'while' loops we could simply map the 'foreach' syntax onto the 'do' loop syntax. Here's what the mapping would look like for the 'foreach' loop: ;; foreach var ( arg ) (do ((lst arg (cdr lst)) (var #f)) ;; command_1 ((null? lst)) ;; ... => (set! var (car lst)) ;; ... expression_1 ;; command_n ... ;; end expression_n ) To get Scheme to do the mapping for you, would use the functions 'define-syntax' and 'syntax-rules' to define a 'macro' so that expressions beginning with 'foreach' would expand into the 'do' format shown above. Here's how you'd define the 'foreach' macro: (define-syntax foreach (syntax-rules () ((foreach var ( arg ) expr ... ) (do ((_lst_ arg (cdr _lst_)) (var #f)) ((null? _lst_)) (set! var (car _lst_)) expr ... )))) Now we can write programs with 'foreach' loops much as we would in C-shell scripts; there might be a few extra parentheses, but presumably that's a small price to pay if you really miss your 'foreach' loops: > (foreach num ( '(1 2 3 4) ) (set! num (* num num num )) (printf "~a ~%" num)) 1 8 27 64 We could do the same sort of thing for 'while' loops: ;; while ( test ) (do () ;; command_1 ((not test)) ;; command_2 => expression_2 ;; ... expression_1 ;; command_n ... ;; end expression_n ) Here's how we could get scheme to perform the mapping for us. (define-syntax while (syntax-rules () ((while ( test ) expr ... ) (do () ((not test)) expr ... )))) > (define num 4) > (while ( (> num 0) ) (printf "~a ~%" num) (set! num (- num 1))) 4 3 2 1 13. Loading Files and Changing Directories Typically definitions are stored in files and 'loaded' into Scheme using the 'load' operation. If "functions.scm" is a file full of 'define' statements, the expression (load "functions.scm") will serve to load and evaluate those definitions making them available for use by other loaded functions. You can also add 'load' statements to other files and in this way decompose a large program into separate components stored in separate files. If you want to be a little more sophisticated you might learn about 'require' but 'load' should suffice for all your course-related projects. At any given time, Scheme has a default directory from which files are loaded. You can determine or change this directory with the 'current-directory' procedure. The 'load' procedure takes absolute or relative path names. If you specify a relative path name then it is treated as relative to the directory returned by 'current-directory'. You can also use 'file-exists?' to determine if a given file exists and 'delete-file' to delete a file. Here are some simple examples illustrating 'current-directory' and 'file-exists?'. > (current-directory) "/u/tld" > (current-directory "~tld/tmp/exercises/final/analysis/") "/u/tld/tmp/exercises/final/analysis/" > (current-directory) "/u/tld/tmp/exercises/final/chatter/bin/" > (file-exists? "README") #t 14. Reading From and Writing To Files Scheme has very sophisticated file handling routines that use what are called 'ports'. There are also some convenient shortcuts for writing to and reading from files that don't require that you go through the complicated process of opening, closing, reading from and writing to ports. Here are two such shortcuts that are particularly convenient: (with-output-to-file "file.txt" (lambda () -procedures 'write', 'display' and 'print' will write to "file.txt" without ever explicitly referring to any port- )) (with-input-from-file "foo.txt" (lambda () -the procedures 'read' and 'read-line' will read from the "file.txt" without ever explicitly referring to any port- )) > (file-exists? "foo.txt") #f > (with-output-to-file "foo.txt" (lambda () (display "It exists now!"))) > (file-exists? "foo.txt") #t > (with-input-from-file "foo.txt" (lambda () (read-line))) "It exists now!" > (delete-file "foo.txt") > (file-exists? "foo.txt") #f You can have any number of reads and writes in the body of 'with-output-to-file' and 'with-input-from-file' respectively. You can also have 'do' loops or 'for-each' statements in the body of the 'lambda' expression as illustrated in the following: > (define words '(one two three four five)) > (with-output-to-file "foo.txt" (lambda () (for-each (lambda (word) (display word) (newline)) words))) > (with-input-from-file "foo.txt" (lambda () (do ((word (read) (read))) ((eof-object? word)) (display word) (newline)))) one two three four five