diff options
Diffstat (limited to 'js/games/nluqo.github.io/~bh/ssch20/io')
-rw-r--r-- | js/games/nluqo.github.io/~bh/ssch20/io | 1333 |
1 files changed, 1333 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/ssch20/io b/js/games/nluqo.github.io/~bh/ssch20/io new file mode 100644 index 0000000..c5e9623 --- /dev/null +++ b/js/games/nluqo.github.io/~bh/ssch20/io @@ -0,0 +1,1333 @@ +\input bkmacs +\pagetag{\typing} +\photo{}{\pspicture{4in}{io}{io}{}\vfill} +\chapter{Input and Output} +\chaptag{\io} + + +In the tic-tac-toe project in Chapter \ttt, we didn't write a complete game +program. We wrote a {\it function\/} that took a board position and {\tt x} +or {\tt o} as arguments, returning the next move. We noted at the time that +a complete game program would also need to carry on a {\it conversation\/} +with the user. Instead of computing and returning one single value, a +\bkidx{conversational}{program} must carry out a sequence of events in time, +reading information from the \idx{keyboard} and displaying other information +on the \idx{screen}. + +Before we complete the tic-tac-toe project, we'll start by +exploring Scheme's mechanisms for \bkidx{interactive}{programming}. + +\backskipsubhd{Printing}{5} +\justidx{printing} + +Up until now, we've never told Scheme to print anything. The programs we've +written have computed values and returned them; we've relied on the +\idx{read-eval-print loop} to print these values.\footnt{The only +exception is that we've used {\tt trace}, which prints messages about +the progress of a computation.} + +But let's say we want to write a program to print out all of the words to +``99 Bottles of Beer on the Wall.'' We could implement a function to produce a +humongous {\it list\/} of the lines of the song, like this: + +{\prgex% +(define (bottles n) + (if (= n 0) + '() + (append (verse n) + (bottles (- n 1))))) +} + +{\medskipamount=4pt\prgexskipamount=9pt\prgexbaselineamount=10pt +{\prgex% +(define (verse n) + (list (cons n '(bottles of beer on the wall)) + (cons n '(bottles of beer)) + '(if one of those bottles should happen to fall) + (cons (- n 1) '(bottles of beer on the wall)) + '())) + +> (bottles 3) +((3 BOTTLES OF BEER ON THE WALL) + (3 BOTTLES OF BEER) + (IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) + (2 BOTTLES OF BEER ON THE WALL) + () + (2 BOTTLES OF BEER ON THE WALL) + (2 BOTTLES OF BEER) + (IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) + (1 BOTTLES OF BEER ON THE WALL) + () + (1 BOTTLES OF BEER ON THE WALL) + (1 BOTTLES OF BEER) + (IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) + (0 BOTTLES OF BEER ON THE WALL) + ()) +} + +\noindent The problem is that we don't want a list. All we +want is to print out the lines of the song; storing them in a data structure +is unnecessary and inefficient. Also, some versions of Scheme would print +the above list like this: + +{\prgex% +((3 BOTTLES OF BEER ON THE WALL) (3 BOTTLES OF BEER) (IF ONE OF + THOSE BOTTLES SHOULD HAPPEN TO FALL) (2 BOTTLES OF BEER ON THE + WALL) () (2 BOTTLES OF BEER ON THE WALL) (2 BOTTLES OF BEER) (IF + ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) (1 BOTTLES OF BEER ON + THE WALL) () (1 BOTTLES OF BEER ON THE WALL) (1 BOTTLES OF BEER) + (IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) (0 BOTTLES OF BEER + ON THE WALL) ()) +} + +\noindent or even all on one line. We can't rely on Scheme's mechanism for +printing lists if we want to be sure of a particular arrangement on the +screen. + +Instead we'll write a program to {\it print\/} a verse, rather +than return it in a list: +\justtt{show} + +{\prgex% +(define (\ufun{bottles} n) + (if (= n 0) + 'burp + (begin (verse n) + (bottles (- n 1))))) + +(define (\ufun{verse} n) + (show (cons n '(bottles of beer on the wall))) + (show (cons n '(bottles of beer))) + (show '(if one of those bottles should happen to fall)) + (show (cons (- n 1) '(bottles of beer on the wall))) + (show '())) + +> (bottles 3) +(3 BOTTLES OF BEER ON THE WALL) +(3 BOTTLES OF BEER) +(IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) +(2 BOTTLES OF BEER ON THE WALL) +() +(2 BOTTLES OF BEER ON THE WALL) +(2 BOTTLES OF BEER) +(IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) +(1 BOTTLES OF BEER ON THE WALL) +() +(1 BOTTLES OF BEER ON THE WALL) +(1 BOTTLES OF BEER) +(IF ONE OF THOSE BOTTLES SHOULD HAPPEN TO FALL) +(0 BOTTLES OF BEER ON THE WALL) +() +BURP +} + +} % skip kludges + +\noindent Notice that Scheme doesn't print an outer set of parentheses. +Each line was printed separately; there isn't one big list containing all of +them.\footnt{We know that it's still not as beautiful as can be, because of +the capital letters and parentheses, but we'll get to that later.} + +Why was ``burp''\ printed at the end? Just because we're printing things +explicitly doesn't mean that the read-eval-print loop stops functioning. We +typed the expression {\tt (bottles~3)}. In the course of evaluating that +expression, Scheme printed several lines for us. But the {\it value\/} of +the expression was the word {\tt burp}, because that's what {\tt bottles} +returned. + +\subhd{Side Effects and Sequencing} + +How does our program work? There are two new ideas here:\ {\it +\bkidx{side}{effect}s\/} and {\it sequencing.} + +Until now, whenever we've invoked a procedure, our only goal has been to get +a return value. The procedures we've used compute and return a value, and do +nothing else. {\tt Show} is different. Although every Scheme procedure +returns a value, the Scheme language standard doesn't specify what +value the printing procedures should return.\footnt{Suppose {\tt show} +returns {\tt \#f} in your version of Scheme. Then you might see + +{\prgex% +> (show 7) +7 +#F +} + +\noindent But since the return value is unspecified, we try to write +programs in such a way that we never use {\tt show}'s return value as the +return value from our procedures. That's why we return values like {\tt +burp}.} Instead, we are interested in their side effects. In other words, +we invoke {\tt show} because we want it to {\it do\/} something, namely, +print its argument on the screen. + +What exactly do we mean by ``side effect''? The kinds of procedures that +we've used before this chapter can compute values, invoke helper procedures, +provide arguments to the helper procedures, and return a value. There may be +a lot of activity going on within the procedure, but the procedure +affects the world outside of itself only by returning a value that some +other procedure might use. {\tt Show} affects the world outside of itself +by putting something on the screen. After {\tt show} has finished its +work, someone who looks at the screen can tell that {\tt show} was +used.\footnt{The term {\it side\/} effect is based on the idea that a +procedure may have a useful return value as its main purpose and may also +have an effect ``on the side.'' It's a misnomer to talk about the +side effect of {\tt show}, since the effect is its main purpose. But nobody +ever says ``side return value''!} + +{\medskipamount=4pt\prgexskipamount=9pt\prgexbaselineamount=10pt +\def\psate{\prgexskipamount=8pt} + +Here's an example to illustrate the difference between values and effects: + +{\prgex% +(define (\ufun{effect} x) + (show x) + 'done) + +(define (\ufun{value} x) + x) + +> (effect '(oh! darling)) +(OH! DARLING) +DONE + +> (value '(oh! darling)) +(OH! DARLING) + +> (bf (effect '(oh! darling))) +(OH! DARLING) +ONE\psate + +> (bf (value '(oh! darling))) +(DARLING) + +> (define (\ufun{lots-of-effect} x) + (effect x) + (effect x) + (effect x)) + +> (define (\ufun{lots-of-value} x) + (value x) + (value x) + (value x)) + +> (lots-of-effect '(oh! darling)) +(OH! DARLING) +(OH! DARLING) +(OH! DARLING) +DONE + +> (lots-of-value '(oh! darling)) +(OH! DARLING) +} + +} % skip kludge + +This example also demonstrates the second new idea, +\idx{sequencing}: Each of {\tt effect}, {\tt lots-of-effect}, +and {\tt lots-of-value} contains more than one expression in +its body. When you invoke such a procedure, Scheme evaluates all +the expressions in the body, in order, and returns the value of the +last one.\footnt{In Chapter \defining, we said that the body of a +procedure was always one single expression. We lied. But as long +as you don't use any procedures with side effects, it doesn't do you +any good to evaluate more than one expression in a body.} This also +works in the body of a {\tt let}, which is really the body of a +procedure, and in each clause of a {\ttidx cond}.\footnt{For example: + +{\zfprgex% +> (cond ((< 4 0) + (show '(how interesting)) + (show '(4 is less than zero?)) + #f) + ((> 4 0) + (show '(more reasonable)) + (show '(4 really is more than zero)) + 'value) + (else + (show '(you mean 4=0?)) + #f)) +(MORE REASONABLE) +(4 REALLY IS MORE THAN ZERO) +VALUE}} + +When we invoked {\tt lots-of-value}, Scheme invoked {\tt value} three times; +it discarded the values returned by the first two invocations, and returned +the value from the third invocation. Similarly, when we invoked {\tt +lots-of-effect}, Scheme invoked {\tt effect} three times and returned the +value from the third invocation. But each invocation of {\tt effect} caused +its argument to be printed by invoking {\tt show}. + +\subhd{The \ttpmb{Begin} Special Form} +\pagetag{\beg} + +The {\tt lots-of-effect} procedure accomplished sequencing by having more +than one expression in its body. This works fine if the sequence of events +that you want to perform is the entire body of a procedure. But in {\tt +bottles} we wanted to include a sequence as one of the alternatives in an +{\tt if} construction. We couldn't just say + +{\prgex% +(define (bottles n) ;; wrong + (if (= n 0) + '() + (verse n) + (bottles (- n 1)))) +} + +\noindent because {\tt if} must have exactly three arguments. Otherwise, +how would {\tt if} know whether we meant {\tt (verse~n)} to be the second +expression in the true case, or the first expression in the false case? + +Instead, to turn the sequence of expressions into a single expression, we +use the \bkidx{special}{form} \ttidx{begin}. It takes any number of +arguments, evaluates them from left to right, and returns the value of the +last one. + +{\prgex% +(define bottles n) + (if (= n 0) + 'burp + (begin (verse n) + (bottles (- n 1))))) +} + +\noindent (One way to think about sequences in procedure bodies is that +every procedure body has an invisible {\tt begin} surrounding it.) + +\subhd{This Isn't Functional Programming} + +Sequencing and side effects are radical departures from the idea of +\bkidx{functional}{programming}. In fact, we'd like to reserve the name {\it +function\/} for something that computes and returns one value, with no side +effects. ``Procedure'' is the general term for the thing that {\tt lambda} +returns---an embodiment of an algorithm. If the algorithm is the kind that +computes and returns a single value without side effects, then we say that +the procedure implements a function.\footnt{Sometimes people sloppily +say that the procedure {\it is\/} a function. In fact, you may hear people +be {\it really\/} sloppy and call a non-functional procedure a function!} + +There is a certain kind of sequencing even in functional programming. If +you say + +{\prgex% +(* (+ 3 4) (- 92 15)) +} + +\noindent it's clear that the addition has to happen before the +multiplication, because the result of the addition provides one of the +arguments to the multiplication. What's new in the sequential programming +style is the {\it emphasis\/} on sequence, and the fact that the expressions +in the sequence are {\it independent\/} instead of contributing values to +each other. In this multiplication problem, for example, we don't care +whether the addition happens before or after the subtraction. If the +addition and subtraction were in a sequence, we'd be using them for +independent purposes: + +{\prgex% +(begin + (show (+ 3 4)) + (show (- 92 15))) +} + +\noindent This is what we mean by being independent. Neither expression +helps in computing the other. And the order matters because we can see +the order in which the results are printed. + +\subhd{Not Moving to the Next Line} + +Each invocation of {\tt show} prints a separate line. What if we +want a program that prints several things on the same line, like this: + +{\prgex% +> (begin (show-addition 3 4) + (show-addition 6 8) + 'done) +3+4=7 +6+8=14 +DONE +} + +\noindent We use \ttidx{display}, which doesn't move to the next line after +printing its argument: + +{\prgex% +(define (\ufun{show-addition} x y) + (display x) + (display '+) + (display y) + (display '=) + (show (+ x y))) +} + +\noindent (The last one is a {\tt show} because we {\it do\/} want to start +a new line after it.) + +What if you just want to print a blank line? You use \ttidx{newline}: + +{\prgex% +(define (verse n) + (show (cons n '(bottles of beer on the wall))) + (show (cons n '(bottles of beer))) + (show '(if one of those bottles should happen to fall)) + (show (cons (- n 1) '(bottles of beer on the wall))) + (newline)) ; replaces (show '()) +} + +In fact, {\tt show} isn't an official Scheme primitive; we wrote it +in terms of {\tt display} and {\tt newline}. + +\subhd{Strings} + +Throughout the book we've occasionally used strings, that is, words enclosed in +double-quote marks so that Scheme will permit the use of punctuation or other +unusual characters. Strings also preserve the case of letters, so they can +be used to beautify our song even more. Since {\it any\/} character can be +in a \idx{string}, including spaces, the easiest thing to do in this case is +to treat all the letters, spaces, and punctuation characters of each line of +the song as one long word. (If we wanted to be able to compute functions of +the individual words in each line, that wouldn't be such a good idea.) + +{\prgex% +(define (\ufun{verse} n) + (display n) + (show " bottles of beer on the wall,") + (display n) + (show " bottles of beer.") + (show "If one of those bottles should happen to fall,") + (display (- n 1)) + (show " bottles of beer on the wall.") + (newline)) +} + +{\medskipamount=4pt\prgexskipamount=8pt\prgexbaselineamount=10pt +{\prgex% +> (verse 6) +6 bottles of beer on the wall, +6 bottles of beer. +If one of those bottles should happen to fall, +5 bottles of beer on the wall. + +#F ; or whatever is returned by (newline) +} + +% \def\vb{\vispc\penalty 0{}} +% \def\vb{\_} +\def\vb{\ } +\noindent It's strange to think of ``{\tt\vb bottles\vb of\vb +beer\vb on\vb the\vb wall,}'' as a single word. But the rule is that +anything inside double quotes counts as a single word. It doesn't have to +be an English word. + +\backskipsubhd{A Higher-Order Procedure for Sequencing}{8} + +Sometimes we want to print each element of a list separately: + +{\prgex% +(define (\ufun{show-list} lst) + (if (null? lst) + 'done + (begin (show (car lst)) + (show-list (cdr lst))))) + +> (show-list '((dig a pony) (doctor robert) (for you blue))) +(DIG A PONY) +(DOCTOR ROBERT) +(FOR YOU BLUE) +DONE +} + +Like other patterns of computation involving lists, this one can be +abstracted into a higher-order procedure. (We can't call it a +``higher-order function'' because this one is for computations with side +effects.) The procedure {\tt \ttidx{for-each}} is part of standard Scheme: + +{\prgex% +> (for-each show '((mean mr mustard) (no reply) (tell me why))) +(MEAN MR MUSTARD) +(NO REPLY) +(TELL ME WHY) +} + +\noindent The value returned by {\tt for-each} is unspecified. + +Why couldn't we just use {\tt map} for this purpose? There are two reasons. +One is just an efficiency issue: {\tt Map} constructs a list containing the +values returned by each of its sub-computations; in this example, it would +be a list of three instances of the unspecified value returned by {\tt +show}. But we aren't going to use that list for anything, so there's no +point in constructing it. The second reason is more serious. In functional +programming, the order of evaluation of subexpressions is unspecified. For +example, when we evaluate the expression + +{\prgex% +(- (+ 4 5) (* 6 7)) +} + +\noindent we don't know whether the addition or the multiplication happens +first. Similarly, the order in which {\tt map} computes the results for +each element is unspecified. That's okay as long as the ultimately returned +list of results is in the right order. But when we are using side effects, +we {\it do\/} care about the order of evaluation. In this case, we want +to make sure that the elements of the argument list are printed from left to +right. {\tt For-each} guarantees this ordering. + +\backskipsubhd{Tic-Tac-Toe Revisited}{8} + +We're working up toward playing a game of tic-tac-toe against the computer. +But as a first step, let's have the computer play against itself. What we +already have is {\tt ttt}, a {\it strategy\/} function:\ one that takes a +board position as argument (and also a letter {\tt x} or {\tt o}) and +returns the chosen next move. In order to play a game of tic-tac-toe, we +need two players; to make it more interesting, each should have its own +strategy. So we'll write another one, quickly, that just moves in the first +empty square it sees: + +{\prgex% +(define (\ufun{stupid-ttt} position letter) + (location '_ position)) + +(define (\ufun{location} letter word) + (if (equal? letter (first word)) + 1 + (+ 1 (location letter (bf word))))) +} + +Now we can write a program that takes two strategies as arguments and +actually plays a game between them. + +{\prgex% +(define (\ufun{play-ttt} x-strat o-strat) + (play-ttt-helper x-strat o-strat '_________ 'x)) + +(define (\ufun{play-ttt-helper} x-strat o-strat position whose-turn) + (cond ((already-won? position (opponent whose-turn)) + (list (opponent whose-turn) 'wins!)) + ((tie-game? position) '(tie game)) + (else (let ((square (if (equal? whose-turn 'x) + (x-strat position 'x) + (o-strat position 'o)))) + (play-ttt-helper x-strat + o-strat + (add-move square whose-turn position) + (opponent whose-turn)))))) +} +} % skip kludge + +\noindent We use a helper procedure because we need to keep track of two +pieces of information besides the strategy procedures:\ the current board +position and whose turn it is ({\tt x} or {\tt o}). The helper procedure +is invoked recursively for each move. First it checks whether the game +is already over (won or tied).\footnt{You wrote the procedures {\tt +already-won?}\ and {\tt tie-game?}\ in Exercises \tttwon\ and \ttttied: + +{\prgex% +(define (\ufun{already-won?} position who) + (member? (word who who who) (find-triples position))) + +(define (\ufun{tie-game?} position) + (not (member? '_ position))) +}} +If not, the helper procedure invokes the current player's strategy procedure, +which returns the square number for the next move. For the recursive call, +the arguments are the same two strategies, the new position after the move, +and the letter for the other player. + +We still need {\tt add-move}, the procedure that takes a square and an old +position as arguments and returns the new position. + +{\prgex% +(define (\ufun{add-move} square letter position) + (if (= square 1) + (word letter (bf position)) + (word (first position) + (add-move (- square 1) letter (bf position))))) + +> (play-ttt ttt stupid-ttt) +(X WINS!) + +> (play-ttt stupid-ttt ttt) +(O WINS!) +} + +\subhd{Accepting User Input} + +The work we did in the last section was purely functional. We didn't print +anything (except the ultimate return value, as always) and we didn't +have to read information from a human player, because there wasn't one. + +You might expect that the structure of an {\it interactive\/} game program +would be very different, with a top-level procedure full of sequential +operations. But the fact is that we hardly have to change anything to turn +this into an interactive game. All we need is a new ``strategy'' procedure +that asks the user where to move, instead of computing a move based on +built-in rules. + +{\prgex% +(define (\ufun{ask-user} position letter) + (print-position position) + (display letter) + (display "'s move: ") + (read)) + +(define (print-position position) ;; first version + (show position)) +} + +\noindent (Ultimately we're going to want a beautiful two-dimensional +display of the current position, but we don't want to get distracted by that +just now. That's why we've written a trivial temporary version.) + +{\prgex% +> (play-ttt ttt ask-user) +____X____ +O'S MOVE: \pmb{1} +O___XX___ +O'S MOVE: \pmb{4} +O__OXXX__ +O'S MOVE: \pmb{3} +OXOOXXX__ +O'S MOVE: \pmb{8} +(TIE GAME) +} + +\noindent What the user typed is just the single digits shown in boldface at +the ends of the lines. + +What's new here is that we invoke the procedure \ttidx{read}. It waits for +you to type a Scheme expression, and returns that expression. Don't +be confused: {\tt Read} does {\it not\/} evaluate what you type. It +returns exactly the same expression that you type: + +{\prgex% +(define (\ufun{echo}) + (display "What? ") + (let ((expr (read))) + (if (equal? expr 'stop) + 'okay + (begin + (show expr) + (echo))))) +} + +{\medskipamount=2pt\prgexskipamount=7.5pt\prgexbaselineamount=10pt +{\prgex% +> (echo) +What? \pmb{hello} +HELLO +What? \pmb{(+ 2 3)} +(+ 2 3) +What? \pmb{(first (glass onion))} +(FIRST (GLASS ONION)) +What? \pmb{stop} +OKAY +} + +\backskipsubhd{Aesthetic Board Display}{9} + +Here's our beautiful position printer: + +{%\setbox2=\hbox{{\ninett +}}\catcode`+=\active\def+{\lower1pt\copy2} + +{\prgex% +(define (\ufun{print-position} position) + (print-row (subword position 1 3)) + (show "-+-+-") + (print-row (subword position 4 6)) + (show "-+-+-") + (print-row (subword position 7 9)) + (newline)) + +(define (\ufun{print-row} row) + (maybe-display (first row)) + (display "|") + (maybe-display (first (bf row))) + (display "|") + (maybe-display (last row)) + (newline)) + +(define (\ufun{maybe-display} letter) + (if (not (equal? letter '_)) + (display letter) + (display " "))) + +(define (\ufun{subword} wd start end) + ((repeated bf (- start 1)) + ((repeated bl (- (count wd) end)) + wd)))\pgfoot +\nobreak} +\nobreak\vfootnt{Alternate version: +\def\fpskip{\vskip 5pt\relax} +{\prgex\fprgexbaselineamount=9.5pt% +(define (subword wd start end) + (cond ((> start 1) (subword (bf wd) (- start 1) (- end 1))) + ((< end (count wd)) (subword (bl wd) start end)) + (else wd))) +} + +\noindent You can take your choice, depending on which you think is easier, +recursion or higher-order functions. +} +} % skip kludge + +Here's how it works: + +{\prgex% +> (print-position '_x_oo__xx) + |X| +-+-+- +O|O| +-+-+- + |X|X +} +} %%%%%%%%% active + kludge %%%%%%%%%%%%% + +\subhd{Reading and Writing Normal Text} + +The {\tt read} procedure works fine as long as what you type looks like a +Lisp program. That is, it reads one expression at a time. In the +tic-tac-toe program the user types a single number, which is a Scheme +expression, so {\tt read} works fine. But what if we want to read more than +one word? + +{\prgex% +(define (music-critic) ;; first version + (show "What's your favorite Beatles song?") + (let ((song (read))) + (show (se "I like" song "too.")))) + +> (music-critic) +What's your favorite Beatles song? +\pmb{She Loves You} +(I like SHE too.) +} + +\noindent If the user had typed the song title in parentheses, then it would +have been a single Scheme expression and {\tt read} would have accepted it. +But we don't want the users of our program to have to be typing parentheses +all the time. + +Scheme also lets you read one character at a time. This allows you to read +any text, with no constraints on its format. The disadvantage is that you +find yourself putting a lot of effort into minor details. We've provided a +procedure {\tt \ttidx{read-line}} that reads one line of input and returns a +sentence. The words in that sentence will contain any punctuation +characters that appear on the line, including parentheses, which are not +interpreted as sublist delimiters by {\tt read-line}. {\tt Read-line} also +preserves the case of letters. + +{\prgex% +(define (music-critic) ;; second version + (read-line) ; See explanation on next page. + (show "What's your favorite Beatles song?") + (let ((song (read-line))) + (show (se "I like" song "too.")))) + +> (music-critic) +What's your favorite Beatles song? +\pmb{She Loves You} +(I like She Loves You too.) +} + +\noindent Why do we call {\tt read-line} and ignore its result at the +beginning of {\tt music-critic}? It has to do with the interaction between +{\tt read-line} and {\tt read}. {\tt Read} treats what you type as a +sequence of Scheme expressions; each invocation of {\tt read} reads one of +them. {\tt Read} pays no attention to formatting details, such as several +consecutive spaces or line breaks. If, for example, you type several +expressions on the same line, it will take several invocations of {\tt read} +to read them all. + +By contrast, {\tt read-line} treats what you type as a sequence of lines, +reading one line per invocation, so it does pay attention to line breaks. + +Either of these ways to read input is sensible in itself, but if you mix +the two, by invoking {\tt read} sometimes and {\tt read-line} sometimes in +the same program, the results can be confusing. Suppose you type a line +containing an expression and your program invokes {\tt read} to read it. +Since there might have been another expression on the line, {\tt read} +doesn't advance to the next line until you ask for the next +expression. So if you now invoke {\tt read-line}, thinking that it will +read another line from the keyboard, it will instead return an empty list, +because what it sees is an empty line---what's left after {\tt read} uses up +the expression you typed. + +You may be thinking, ``But {\tt music-critic} doesn't call {\tt read}!'' +That's true, but Scheme itself used {\tt read} to read the expression that +you used to invoke {\tt music-critic}. So the first invocation of {\tt +read-line} is needed to skip over the spurious empty line. + +Our solution works only if {\tt music-critic} is invoked directly at a +Scheme prompt. If {\tt music-critic} were a subprocedure of some larger +program that has already called {\tt read-line} before calling {\tt +music-critic}, the extra {\tt read-line} in {\tt music-critic} would really +read and ignore a useful line of text. + +If you write a procedure using {\tt read-line} that will sometimes be called +directly and sometimes be used as a subprocedure, you can't include an extra +{\tt read-line} call in it. Instead, when you call your procedure directly +from the Scheme prompt, you must say + +{\prgex% +> (begin (read-line) (my-procedure)) +} + +Another technical detail about {\tt read-line} is that since +it preserves the capitalization of words, its result may +include strings, which will be shown in quotation marks if you return the +value rather than {\tt show}ing it: + +{\prgex% +(define (music-critic-return) + (read-line) + (show "What's your favorite Beatles song?") + (let ((song (read-line))) + (se "I like" song "too."))) + +> (music-critic-return) +What's your favorite Beatles song? +\pmb{She Loves You} +("I like" "She" "Loves" "You" "too.") +} + +We have also provided {\tt \ttidx{show-line},} which takes a sentence +as argument. It prints the sentence without surrounding parentheses, +followed by a newline. (Actually, it takes any list as argument; it prints +all the parentheses except for the outer ones.) + +{\prgex% +(define (\ufun{music-critic}) + (read-line) + (show "What's your favorite Beatles song?") + (let ((song (read-line))) + (show-line (se "I like" song "too.")))) + +> (music-critic) +What's your favorite Beatles song? +\pmb{She Loves You} +I like She Loves You too. +} + +The difference between {\tt show} and {\tt show-line} isn't +crucial. It's just a matter of a pair of parentheses. The point is that +{\tt read-line} and {\tt show-line} go together. {\tt Read-line} reads a +bunch of disconnected words and combines them into a sentence. {\tt +Show-line} takes a sentence and prints it as if it were a bunch of +disconnected words. Later, when we read and write files in Chapter +\files, this ability to print in the same form in which we read will be +important. + +\subhd{Formatted Text} + +We've been concentrating on the use of sequential programming with explicit +\pagetag{\spformat} +printing instructions for the sake of conversational programs. Another +common application of sequential printing is to display tabular information, +such as columns of numbers. The difficulty is to get the numbers to line up +so that corresponding digits are in the same position, even when the numbers +have very widely separated values. The +\ttidx{align} function can be used to convert a number to a printable word +with a fixed number of positions before and after the decimal point: + +{\prgex% +(define (square-root-table nums) + (if (null? nums) + 'done + (begin (display (align (car nums) 7 1)) + (show (align (sqrt (car nums)) 10 5)) + (square-root-table (cdr nums))))) + +> (square-root-table '(7 8 9 10 20 98 99 100 101 1234 56789)) + 7.0 2.64575 + 8.0 2.82843 + 9.0 3.00000 + 10.0 3.16228 + 20.0 4.47214 + 98.0 9.89949 + 99.0 9.94987 + 100.0 10.00000 + 101.0 10.04988 + 1234.0 35.12834 +56789.0 238.30443 +DONE +} + +\noindent {\tt Align} takes three arguments. The first is the value to be +displayed. The second is the width of the column in which it will be +displayed; the returned value will be a word with that many characters in it. +The third argument is the number of digits that should be displayed to the +right of the decimal point. (If this number is zero, then no decimal point +will be displayed.) The width must be great enough to include all the +digits, as well as the decimal point and minus sign, if any. + +As the program example above indicates, {\tt align} does not print +anything. It's a function that returns a value suitable for printing with +{\tt display} or {\tt show}. + +What if the number is too big to fit in the available space? + +{\prgex% +> (align 12345679 4 0) +"123+" +} + +\noindent {\tt Align} returns a word containing the first few digits, +as many as fit, ending with a plus sign to indicate that part of the value +is missing. + +{\tt Align} can also be used to include non-numeric text in columns. If +the first argument is not a number, then only two arguments are needed; the +second is the column width. In this case {\tt align} returns a word with +extra spaces at the right, if necessary, so that the argument word will +appear at the left in its column: + +{\prgex% +(define (\ufun{name-table} names) + (if (null? names) + 'done + (begin (display (align (cadar names) 11)) + (show (caar names)) + (name-table (cdr names))))) + +> (name-table '((john lennon) (paul mccartney) + (george harrison) (ringo starr))) +LENNON JOHN +MCCARTNEY PAUL +HARRISON GEORGE +STARR RINGO +DONE +} + +\noindent As with numbers, if a non-numeric word won't fit in the allowed +space, {\tt align} returns a partial word ending with a plus sign. + +This {\tt align} function is not part of standard Scheme. Most programming +languages, including some versions of Scheme, offer much more elaborate +formatting capabilities with many alternate ways to represent both numbers +and general text. Our version is a minimal capability to show the flavor +and to meet the needs of projects in this book. + +\subhd{Sequential Programming and Order of Evaluation} + +Our expanded tic-tac-toe program includes both functional and sequential +parts. The program computes its strategy functionally but uses sequences +of commands to control the {\it \bkidx{user}{interface}\/} by alternately +printing information to the screen and reading information from the keyboard. + +By adding sequential programming to our toolkit, we've increased our ability +to write interactive programs. But there is a cost that goes along with +this benefit: We now have to pay more attention to the order of events than +we did in purely functional programs. + +The obvious concern about order of events is that sequences of {\tt show} +expressions must come in the order in which we want them to appear, and {\tt +read} expressions must fit into the sequence properly so that the user is +asked for the right information at the right time. + +But there is another, less obvious issue about order of events. When the +evaluation of expressions can have side effects in addition to returning +values, the order of evaluation of argument subexpressions becomes important. +Here's an example to show what we mean. Suppose we type the expression + +{\prgex% +(list (+ 3 4) (- 10 2)) +} + +\noindent The answer, of course, is {\tt (7~8)}. It doesn't matter whether +Scheme computes the seven first (left to right) or the eight first (right to +left). But here's a similar example in which it {\it does\/} matter: + +{\prgex% +(define (\ufun{show-and-return} x) + (show x) + x) + +> (list (show-and-return (+ 3 4)) (show-and-return (- 10 2))) +8 +7 +(7 8) +} + +\noindent The value that's ultimately returned, in this example, is the same +as before. But the two numeric values that go into the list are also +printed separately, so we can see which is computed first. (We've shown +the case of right-to-left computation; your Scheme might be different.) + +Suppose you want to make sure that the seven prints first, regardless of +which order your Scheme uses. You could do this: + +{\prgex% +> (let ((left (show-and-return (+ 3 4)))) + (list left (show-and-return (- 10 2)))) +7 +8 +(7 8) +} + +\noindent The expression in the body of a {\tt let} can't be evaluated until +the {\tt let} variables (such as {\tt left}) have had their values computed. + +It's hard to imagine a practical use for the artificial {\tt +show-and-return} procedure, but a similar situation arises whenever we use +{\tt read}. Suppose we want to write a procedure to ask a person for his or +her full name, returning a two-element list containing the first and last +name. A natural mistake to make would be to write this procedure: + +{\prgex% +(define (ask-for-name) ;; wrong + (show "Please type your first name, then your last name:") + (list (read) (read))) + +> (ask-for-name) +Please type your first name, then your last name: +\pmb{John +Lennon} +(LENNON JOHN) +} + +\noindent What went wrong? We happen to be using a version of Scheme that +evaluates argument subexpressions from right to left. Therefore, the word +{\tt John} was read by the rightmost call to {\tt read}, which provided the +second argument to {\tt list}. The best solution is to use {\tt let} as we +did above: + +{\prgex% +(define (\ufun{ask-for-name}) + (show "Please type your first name, then your last name:") + (let ((first-name (read))) + (list first-name (read)))) +} + +Even this example looks artificially simple, because of the two invocations +of {\tt read} that are visibly right next to each other in the erroneous +version. But look at {\tt play-ttt-helper}. The word {\tt read} doesn't +appear in its body at all. But when we invoke it using {\tt ask-user} as +the strategy procedure for {\tt x}, the expression + +{\prgex% +(x-strat position 'x) +} + +\noindent hides an invocation of {\tt read}. The structure of {\tt +play-ttt-helper} includes a {\tt let} that controls the timing of that {\tt +read}. (As it turns out, in this particular case we could have gotten away +with writing the program without {\tt let}. The hidden invocation of {\tt +read} is the only subexpression with a side effect, so there aren't two +effects that might get out of order. But we had to think carefully about +the program to be sure of that.) + +\subhd{Pitfalls} + +\pit It's easy to get confused about what is printed explicitly by your +\justidx{printing} +program and what is printed by Scheme's read-eval-print loop. Until now, +{\it all\/} printing was of the second kind. Here's an example that doesn't +do anything very interesting but will help make the point clear: + +{\prgex% +(define (name) + (display "MATT ") + 'wright) + +> (name) +MATT WRIGHT +} + +\noindent At first glance it looks as if putting the word ``Matt'' inside a +call to {\tt display} is unnecessary. After all, the word {\tt wright} is +printed even without using {\tt display}. But watch this: + +{\prgex% +> (bf (name)) +MATT RIGHT +} + +\noindent Every time you invoke {\tt name}, whether or not as the entire +expression used at a Scheme prompt, the word {\tt MATT} is printed. But +the word {\tt wright} is {\it returned,\/} and may or may not be printed +depending on the context in which {\tt name} is invoked. + +\pit A sequence of expressions returns the value of the {\it last\/} +expression. If that isn't what you want, you must remember the value you +want to return using {\tt let}: + +{\prgex% +(let ((result (compute-this-first))) + (begin + (compute-this-second) + (compute-this-third) + result)) +} + +\pit Don't forget that the first call to {\tt read-line}, or any call to +{\tt read-line} after a call to {\tt read}, will probably read the empty +line that {\tt read} left behind. + +\pit Sometimes you want to use what the user typed more than once in your +program. But don't forget that {\tt read} has an effect as well as a return +value. Don't try to read the same expression twice: + +{\prgex% +(define (ask-question question) ;; wrong + (show question) + (cond ((equal? (read) 'yes) #t) + ((equal? (read) 'no) #f) + (else (show "Please answer yes or no.") + (ask-question question)))) +} + +\noindent If the answer is {\tt yes}, this procedure will work fine. But if +not, the second invocation of {\tt read} will read a second expression, not +test the same expression again as intended. To avoid this problem, invoke +{\tt read} only once for each expression you want to read, and use {\tt let} +to remember the result: + +{\prgex% +(define (\ufun{ask-question} question) + (show question) + (let ((answer (read))) + (cond ((equal? answer 'yes) #t) + ((equal? answer 'no) #f) + (else (show "Please answer yes or no.") + (ask-question question))))) +} + +\esubhd{Boring Exercises} + +{\exercise +What happens when we evaluate the following expression? What is printed, +and what is the return value? Try to figure it out in your head before you +try it on the computer. + +{\prgex% +(cond ((= 2 3) (show '(lady madonna)) '(i call your name)) + ((< 2 3) (show '(the night before)) '(hello little girl)) + (else '(p.s. i love you))) +}} + +\solution +{\tt (THE NIGHT BEFORE)} is printed. + +{\tt (HELLO LITTLE GIRL)} is returned. + +@ + +{\exercise +What does {\tt newline} return in your version of Scheme? +} + +{\exercise +Define {\tt show} in terms of {\tt newline} and {\tt display}. +} + +\solution +{\prgex% +(define (show stuff) + (display stuff) + (newline)) +} +@ + +\esubhd{Real Exercises} + +{\exercise +Write a program that carries on a conversation like the following example. +What the user types is in boldface. + +{\prgex% +> \pmb{(\ufun{converse})} +Hello, I'm the computer. What's your name? \pmb{Brian Harvey} +Hi, Brian. How are you? \pmb{I'm fine.} +Glad to hear it. +}} + +\solution + +Here's a boring version that's glad to hear anything about how you're +doing. Naturally you could make it do a lot more if you were so inclined. + +{\prgex% +(define (converse) + (read-line) + (display "Hello, I'm the computer. What's your name? ") + (let ((name (read-line))) + (display (word "Hi, " (first name) ". How are you? ")) + (read-line) + (show "Glad to hear it."))) +} +@ + +{\exercise +Our {\tt name-table} procedure uses a fixed width for the column containing +the last names of the people in the argument list. Suppose that instead of +liking British-invasion music you are into late romantic Russian composers: + +{\prgex% +> (name-table '((piotr tchaikovsky) (nicolay rimsky-korsakov) + (sergei rachmaninov) (modest musorgsky))) +} + +\noindent Alternatively, perhaps you like jazz: + +{\prgex% +> (name-table '((bill evans) (paul motian) (scott lefaro))) +} + +\noindent Modify {\tt name-table} so that it figures out the longest last +name in its argument list, adds two for spaces, and uses that number as the +width of the first column. +} + +\solution +{\prgex% +(define (name-table names) + (if (null? names) + 'done + (nt-help names + (+ 2 (reduce max (map (lambda (nm) (count (last nm))) + names)))))) + +(define (nt-help names width) + (if (null? names) + 'done + (begin (display (align (cadar names) width)) + (show (caar names)) + (nt-help (cdr names) width)))) +} + +The {\tt null?} test in {\tt name-table} is needed only for the case +in which the user gives an empty argument; the {\tt null?} test that +serves as the base case for the recursion is the one in {\tt nt-help}. + +@ + +{\exercise +The procedure {\tt ask-user} isn't robust. What happens if you type +something that isn't a number, or isn't between 1 and 9? Modify it to check +that what the user types is a number between 1 and 9. If not, it should +print a message and ask the user to try again. +} + +\solution +The changed parts of the procedure are shown here in boldface. + +{\prgex% +(define (ask-user position letter) + (print-position position) + (display letter) + (display "'s move: ") + \pmb{(let ((answer (read)))} + \pmb{(if (and (integer? answer) (>= answer 1) (<= answer 9))} + \pmb{answer} + \pmb{(begin (show "That's not a move, silly!")} + \pmb{(ask-user position letter)))))} +} +@ + +{\exercise +Another problem with {\tt ask-user} is that it allows a user to request a +square that isn't free. If the user does this, what happens? Fix {\tt +ask-user} to ensure that this can't happen. +} + +\solution +If the user asks for a square that's already taken, the square will +be reassigned to the user. The following solution assumes that the +previous exercise is also included. +The changed parts of the procedure are shown here in boldface. + + +{\prgex% +(define (ask-user position letter) + (print-position position) + (display letter) + (display "'s move: ") + (let ((answer (read))) + \pmb{(cond ((not} (and (integer? answer) (>= answer 1) (<= answer 9))) + (show "That's not a move, silly!") + (ask-user position letter)) + \pmb{((not (equal? '_ (item answer position)))} + \pmb{(show "That square is occupied.")} + \pmb{(ask-user position letter))} + \pmb{(else} answer)))) +} +@ + +{\exercise +At the end of the game, if the computer wins or ties, you never find out +which square it chose for its final move. Modify the program to correct +this. (Notice that this exercise requires you to make {\tt play-ttt-helper} +non-functional.) +} + +\solution +The changed parts of the procedure are shown here in boldface. + +{\prgex% +(define (play-ttt-helper x-strat o-strat position whose-turn) + (cond ((already-won? position (opponent whose-turn)) + \pmb{(print-position position)} + (list (opponent whose-turn) 'wins!)) + ((tie-game? position) + \pmb{(print-position position)} + '(tie game)) + (else (let ((square (if (equal? whose-turn 'x) + (x-strat position 'x) + (o-strat position 'o)))) + (play-ttt-helper x-strat + o-strat + (add-move square whose-turn position) + (opponent whose-turn)))))) +} +@ + +{\exercise +The way we invoke the game program isn't very user-friendly. Write a +procedure {\tt game} that asks you whether you wish to play {\tt x} or {\tt +o}, then starts a game. (By definition, {\tt x} plays first.) Then write a +procedure {\tt games} that allows you to keep playing repeatedly. It +can ask ``do you want to play again?''\ after each game. (Make sure that +the outcome of each game is still reported, and that the user can choose +whether to play {\tt x} or {\tt o} before each game.) +} + +\solution +{\prgex% +(define (game) + (display "Do you want to play X or O? ") + (let ((letter (read))) + (cond ((equal? letter 'x) + (play-ttt ask-user ttt)) + ((equal? letter 'o) + (play-ttt ttt ask-user)) + (else (show "Please type X or O!") + (game))))) + +(define (games) + (show (game)) + (display "Do you want to play again (Y or N)? ") + (if (another?) + (games) + "Thank you for playing, have a day.")) + +(define (another?) + (let ((letter (read))) + (cond ((equal? letter 'y) #t) + ((equal? letter 'n) #f) + (else (show "C'mon, Y or N!") + (another?))))) +} + +{\tt Game} and {\tt games} both ask a question, and both include a +check for invalid answers. But {\tt game} is able to repeat the +question itself, if the answer was invalid, whereas {\tt games} +uses a helper procedure {\tt another?} to ask the question. The +reason for this difference is that {\tt games} plays a game before +asking its question, whereas the question is the first thing in +{\tt game}. If {\tt games} were written as a single procedure, +an invalid answer would result in playing another game before +asking again. + +@ + +\bye |