diff options
author | elioat <elioat@tilde.institute> | 2023-08-23 07:52:19 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2023-08-23 07:52:19 -0400 |
commit | 562a9a52d599d9a05f871404050968a5fd282640 (patch) | |
tree | 7d3305c1252c043bfe246ccc7deff0056aa6b5ab /js/games/nluqo.github.io/~bh/ssch21 | |
parent | 5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff) | |
download | tour-562a9a52d599d9a05f871404050968a5fd282640.tar.gz |
*
Diffstat (limited to 'js/games/nluqo.github.io/~bh/ssch21')
-rw-r--r-- | js/games/nluqo.github.io/~bh/ssch21/functions-implement | 1026 | ||||
-rw-r--r-- | js/games/nluqo.github.io/~bh/ssch21/functions-implement.html | 939 |
2 files changed, 1965 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/ssch21/functions-implement b/js/games/nluqo.github.io/~bh/ssch21/functions-implement new file mode 100644 index 0000000..0661202 --- /dev/null +++ b/js/games/nluqo.github.io/~bh/ssch21/functions-implement @@ -0,0 +1,1026 @@ +\input bkmacs +\pagetag{\oz} +\photo{Once you see how it works, it's not so mysterious.}{\pspicture{4in}{oz}{oz}{}} + +\let\ttfont=\twelvett +\chapter{Example: The\/ {\ttfont Functions} Program} +\chaptag{\fimp} +\normaltext + +In Chapter \functions\ you used a program called \ttidx{functions} to explore +some of Scheme's primitive functions. Now we're going to go back to that +program from the other point of view: instead of using the program to learn +about functions, we're going to look at how the program works as an example +of programming with input and output. + +\backskipsubhd{The Main Loop}{7} + +The {\tt functions} program is an infinite loop similar to Scheme's +\idx{read-eval-print loop}. It reads in a function name and some arguments, +prints the result of applying that function to those arguments, and then +does the whole thing over again. + +There are some complexities, though. The {\tt functions} program keeps +asking you for arguments until it has enough. This means that the {\tt +read} portion of the loop has to read a function name, figure out how many +arguments that procedure takes, and then ask for the right number of +arguments. On the other hand, each argument is an implicitly quoted datum +rather than an expression to be evaluated; the {\tt functions} evaluator +avoids the recursive complexity of arbitrary subexpressions within +expressions. (That's why we wrote it:\ to focus attention on one function +invocation at a time, rather than on the composition of functions.) +Here's the main loop: + +{\prgex% +(define (\ufun{functions-loop}) + (let ((fn-name (get-fn))) + (if (equal? fn-name 'exit) + "Thanks for using FUNCTIONS!" + (let ((args (get-args (arg-count fn-name)))) + (if (not (in-domain? args fn-name)) + (show "Argument(s) not in domain.") + (show-answer (apply (scheme-procedure fn-name) args))) + (functions-loop))))) +} + +\noindent This invokes a lot of helper procedures. {\tt Arg-count} takes +the name of a procedure as its argument and returns the number of arguments +that the given procedure takes. {\tt In-domain?} takes a list and the name +of a procedure as arguments; it returns {\tt \#t} if the elements of the list +are valid arguments to the given procedure. {\tt Scheme-procedure} takes a +name as its argument and returns the Scheme procedure with the given name. +We'll get back to these helpers later. + +The other helper procedures are the ones that do the input and output. The +actual versions are more complicated because of error checking; we'll show +them to you later. + +{\prgex% +(define (get-fn) ;; first version + (display "Function: ") + (read)) + +(define (\ufun{get-args} n) + (if (= n 0) + '() + (let ((first (get-arg))) + (cons first (get-args (- n 1)))))) + +(define (get-arg) ;; first version + (display "Argument: ") + (read)) + +(define (\ufun{show-answer} answer) + (newline) + (display "The result is: ") + (if (not answer) + (show "#F") + (show answer)) + (newline)) +} + +\noindent (That weird {\tt if} expression in {\tt show-answer} is needed +because in some old versions of Scheme the empty list means the same as {\tt +\#f}. We wanted to avoid raising this issue in Chapter \functions, so we +just made sure that false values always printed as {\tt \#F}.) + +\subhd{The Difference between a Procedure and Its Name} + +You may be wondering why we didn't just say + +{\prgex% +(show-answer (apply fn-name args)) +} + +\noindent in the definition of {\tt functions-loop}. Remember that the value of +the variable {\tt fn-name} comes from {\tt get-fn}, which invokes {\tt read}. +Suppose you said + +{\medskipamount=3pt\prgexskipamount=10pt + +{\prgex% +(define x (read)) +} + +\noindent and then typed + +{\prgex% +(+ 2 3) +} + +\noindent The value of {\tt x} would be the three element list {\tt +(+~2~3)}, not the number five. + +Similarly, if you type ``butfirst,'' then read will return the {\it +word\/} {\tt butfirst}, not the procedure of that name. So we need a way to +turn the name of a function into the procedure itself. + +\backskipsubhd{The Association List of Functions}{4} + +We accomplish this by creating a huge association list that contains +all of the functions the program knows about. Given a word, such as +{\tt butfirst}, we need to know three things: + +\medskip +{\parindent=1em\parskip=8pt +\bb The Scheme procedure with that name (in this case, the {\tt butfirst} +procedure). +\bb The number of arguments required by the given procedure +(one).\footnt{Some Scheme procedures can accept any number of arguments, +but for the purposes of the {\tt functions} program we restrict these +procedures to their most usual case, such as two arguments for~{\tt +}.} +\bb The types of arguments required by the given procedure (one word or +sentence, which must not be empty). + +}\medskip + +We need to know the number of arguments the procedure requires because the +program prompts the user individually for each argument; it has to know how +many to ask for. Also, it needs to know the domain of each function so +it can complain if the arguments you give it are not in the +domain.\footnt{Scheme would complain all by itself, of course, but would +then stop running the {\tt functions} program. We want to catch the error +before Scheme does, so that after seeing the error message you're still in +{\tt functions}. As we mentioned in Chapter \implhof, a program meant for +beginners, such as the readers of Chapter \functions, should be +especially robust.} + +This means that each entry in the association list is a list of four +elements: +% . It looks like this: + +{\prgex% +(define *the-functions* ;; partial listing + (list (list '* * 2 (lambda (x y) (and (number? x) (number? y)))) + (list '+ + 2 (lambda (x y) (and (number? x) (number? y)))) + (list 'and (lambda (x y) (and x y)) 2 + (lambda (x y) (and (boolean? x) (boolean? y)))) + (list 'equal? equal? 2 (lambda (x y) #t)) + (list 'even? even? 1 integer?) + (list 'word word 2 (lambda (x y) (and (word? x) (word? y)))))) +} + +\noindent The real list is much longer, of course, but you get the +idea.\footnt{Since {\tt and} is a special form, we can't just say + +{\prgex% +(list 'and and 2 (lambda (x y) (and (boolean? x) (boolean? y)))) +} + +That's because special forms can't be elements of lists. Instead, we have +to create a normal procedure that can be put in a list but computes the same +function as {\tt and}: + +{\prgex% +(lambda (x y) (and x y)) +} + +\noindent We can get away with this because in the {\tt functions} program we +don't care about argument evaluation, so it doesn't matter that {\tt and} is +a special form. We do the same thing for {\tt if} and {\tt or}.} It's a +convention in Scheme programming that names of global variables used +throughout a program are surrounded by {\tt *}asterisks{\tt *} to +distinguish them from parameters of procedures. + +Here are the selector procedures for looking up information in this a-list: + +{\prgex% +(define (\ufun{scheme-procedure} fn-name) + (cadr (assoc fn-name *the-functions*))) + +(define (\ufun{arg-count} fn-name) + (caddr (assoc fn-name *the-functions*))) + +(define (\ufun{type-predicate} fn-name) + (cadddr (assoc fn-name *the-functions*))) +} + +\subhd{Domain Checking} + +} % skip kludge + +Note that we represent the domain of a procedure by another +procedure.\footnt{The domain of a procedure is a set, and sets are generally +represented in programs as lists. You might think that we'd have to store, +for example, a list of all the legal arguments to {\tt butfirst}. But that +would be impossible, since that list would have to be infinitely large. +Instead, we can take advantage of the fact that the only use we make of this +set is membership testing, that is, finding out whether a particular argument +is in a function's domain.} Each domain-checking procedure, or {\it type +predicate,\/} takes the same arguments as the procedure whose domain it +checks. For example, the type predicate for {\tt +} is + +{\prgex% +(lambda (x y) (and (number? x) (number? y))) +} + +\noindent The type predicate returns {\tt \#t} if its arguments +are valid and {\tt \#f} otherwise. So in the case of {\tt +}, any two +numbers are valid inputs, but any other types of arguments aren't. + +Here's the {\tt in-domain?} predicate: + +{\prgex% +(define (\ufun{in-domain?} args fn-name) + (apply (type-predicate fn-name) args)) +} + +Of course, certain type predicates are applicable to more than one +procedure. It would be silly to type + +{\prgex% +(lambda (x y) (and (number? x) (number? y))) +} + +\noindent for {\tt +}, {\tt -}, {\tt =}, and so on. Instead, we give this +function a name: + +{\prgex% +(define (\ufun{two-numbers?} x y) + (and (number? x) (number? y))) +} + +\noindent We then refer to the type predicate by name in the a-list: + +{\prgex% +(define *the-functions* ;; partial listing, revised + (list (list '* * 2 two-numbers?) + (list '+ + 2 two-numbers?) + (list 'and (lambda (x y) (and x y)) 2 + (lambda (x y) (and (boolean? x) (boolean? y)))) + (list 'equal? equal? 2 (lambda (x y) #t)) + (list 'even? even? 1 integer?) + (list 'word word 2 (lambda (x y) (and (word? x) (word? y)))))) +} + +Some of the type predicates are more complicated. For example, +here's the one for the {\tt member?} and {\tt appearances} functions: + +{\prgex% +(define (\ufun{member-types-ok?} small big) + (and (word? small) + (or (sentence? big) (and (word? big) (= (count small) 1))))) +} + +\noindent {\tt Item} also has a complicated domain: + +{\prgex% +(lambda (n stuff) + (and (integer? n) (> n 0) + (word-or-sent? stuff) (<= n (count stuff)))) +} + +\noindent This invokes {\tt word-or-sent?}, which is itself the type +predicate for the {\tt count} procedure: + +{\prgex% +(define (word-or-sent? x) + (or (word? x) (sentence? x))) +} + +On the other hand, some are less complicated. {\tt Equal?} will accept any +two arguments, so its type predicate is just + +{\prgex% +(lambda (x y) #t) +} + +The complete listing at the end of the chapter shows the details of all these +procedures. Note that the {\tt functions} program has a more restricted +idea of domain than Scheme does. For example, in Scheme + +{\prgex% +(and 6 #t) +} + +\noindent returns {\tt \#t} and does not generate an error. But in +the {\tt functions} program the argument {\tt 6} is considered out of the +domain.\footnt{Why did we choose to restrict the domain? We were trying +to make the point that invoking a procedure makes sense only with appropriate +arguments; that point is obscured by the complicating fact that Scheme +interprets any non-{\tt \#f} value as true. In the {\tt functions} program, +where composition of functions is not allowed, there's no benefit to Scheme's +more permissive rule.} + +If you don't like math, just ignore the domain predicates for the +mathematical primitives; they involve facts about the domains of math +functions that we don't expect you to know.\footnt{A reason that we +restricted the domains of some mathematical functions is to protect +ourselves from the fact that some version of Scheme support complex numbers +while others do not. We wanted to write one version of {\tt functions} that +would work in either case; sometimes the easiest way to avoid possible +problems was to restrict some function's domain.} + +\subhd{Intentionally Confusing a Function with Its Name} + +{\medskipamount=5pt\prgexskipamount=10pt + +Earlier we made a big deal about the difference between a procedure and its +name, to make sure you wouldn't think you can apply the {\it word\/} {\tt +butfirst} to arguments. But the {\tt functions} program completely hides +this distinction from the user: + +{\prgex% +Function: count +Argument: butlast + +The result is: 7 + +Function: every +Argument: butlast +Argument: (helter skelter) + +The result is: (HELTE SKELTE) +} + +When we give {\tt butlast} as an argument to {\tt count}, it's as if we'd said + +{\prgex% +(count 'butlast) +} + +\noindent In other words, it's taken as a word. But when we give {\tt +butlast} as an argument to {\tt every}, it's as if we'd said + +{\prgex% +(every butlast '(helter skelter)) +} + +How can we treat some arguments as quoted and others not? The way this +works is that {\it everything\/} is considered a word or a sentence by the +{\tt functions} program. The higher-order functions {\tt every} and {\tt +keep} are actually represented in the {\tt functions} implementation by +Scheme procedures that take the {\it name\/} of a function as an argument, +instead of a procedure itself as the ordinary versions do: + +{\prgex% +(define (\ufun{named-every} fn-name list) + (every (scheme-procedure fn-name) list)) + +(define (\ufun{named-keep} fn-name list) + (keep (scheme-procedure fn-name) list)) + +> (every first '(another girl)) +(A G) +> (named-every 'first '(another girl)) +(A G) +> (every 'first '(another girl)) +ERROR: ATTEMPT TO APPLY NON-PROCEDURE FIRST +} + +} % end skip kludge + +\noindent This illustration hides a subtle point. When we invoked {\tt +named-every} at a Scheme prompt, we had to quote the word {\tt first} +that we used as its argument. But when you run the {\tt functions} program, +you don't quote anything. The point is that {\tt functions} provides an +evaluator that uses a different notation from Scheme's notation. It may be +clearer if we show an interaction with an imaginary version of {\tt +functions} that {\it does\/} use Scheme notation: + +{\prgex% +Function: first +Non-Automatically-Quoted-Argument: 'datum + +The result is: D + +Function: first +Non-Automatically-Quoted-Argument: datum + +ERROR: THE VARIABLE DATUM IS UNBOUND. +} + +\noindent We didn't want to raise the issue of quoting at that early point +in the book, so we wrote {\tt functions} so that {\it every\/} argument +is automatically quoted. Well, if that's the case, it's true even when +we're invoking {\tt every}. If you say + +{\prgex% +Function: every +Argument: first +\ellipsis +} + +\noindent then by the rules of the {\tt functions} program, that argument is +the quoted word {\tt first}. So {\tt named-every}, the procedure that +pretends to be {\tt every} in the {\tt functions} world, has to ``un-quote'' +that argument by looking up the corresponding procedure. + +\subhd{More on Higher-Order Functions} + +One of the higher-order functions that you can use in the {\tt functions} +program is called {\tt number-of-arguments}. It takes a procedure (actually +the name of a procedure, as we've just been saying) as argument and returns +the number of arguments that that procedure accepts. This example is +unusual because there's no such function in Scheme. (It would be +complicated to define, for one thing, because some Scheme procedures can +accept a variable number of arguments. What should {\tt +number-of-arguments} return for such a procedure?) + +The implementation of {\tt number-of-arguments} makes use of the same a-list +of functions that the {\tt functions} evaluator itself uses. Since the +{\tt functions} program needs to know the number of arguments for every +procedure anyway, it's hardly any extra effort to make that information +available to the user. We just add an entry to the a-list: + +{\prgex% +(list '\ufun{number-of-arguments} arg-count 1 valid-fn-name?) +} + +\noindent The type predicate merely has to check that the argument +is found in the a-list of functions: + +{\prgex% +(define (\ufun{valid-fn-name?} name) + (assoc name *the-functions*)) +} + +The type checking for the arguments to {\tt every} and {\tt keep} is +unusually complicated because what's allowed as the second argument (the +collection of data) depends on which function is used as the first argument. +For example, it's illegal to compute + +{\prgex% +(every square '(think for yourself)) +} + +\noindent even though either of those two arguments would be allowable if we +changed the other one: + +{\prgex% +> (every square '(3 4 5)) +(9 16 25) + +> (every first '(think for yourself)) +(T F Y) +} + +The type-checking procedures for {\tt every} and {\tt keep} use a common +subprocedure. The one for {\tt every} is + +{\prgex% +(lambda (fn stuff) + (hof-types-ok? fn stuff word-or-sent?)) +} + +\noindent and the one for {\tt keep} is + +{\prgex% +(lambda (fn stuff) + (hof-types-ok? fn stuff boolean?)) +} + +\noindent The third argument specifies what types of results {\tt fn} must +return when applied to the elements of {\tt stuff}. + +{\prgex% +(define (hof-types-ok? fn-name stuff range-predicate) + (and (valid-fn-name? fn-name) + (= 1 (arg-count fn-name)) + (word-or-sent? stuff) + (empty? (keep (lambda (element) + (not ((type-predicate fn-name) element))) + stuff)) + (null? (filter (lambda (element) + (not (range-predicate element))) + (map (scheme-procedure fn-name) + (every (lambda (x) x) stuff)))))) +} + +\noindent This says that the function being used as the first argument must +be a one-argument function (so you can't say, for example, {\tt every} of +{\tt word} and something); also, {\it each element\/} of the second argument +must be an acceptable argument to that function. (If you {\tt keep} the +unacceptable arguments, you get nothing.) Finally, each invocation of the +given function on an element of {\tt stuff} must return an object of the +appropriate type:\ words or sentences for {\tt every}, true or false for +{\tt keep}.\footnt{That last argument to {\tt and} is complicated. The +reason we use {\tt map} instead of {\tt every} is that the results of the +invocations of {\tt fn} might not be words or sentences, so {\tt every} +wouldn't accept them. But {\tt map} has its own limitation: It won't +accept a word as the {\tt stuff} argument. So we use {\tt every} to turn +{\tt stuff} into a sentence---which, as you know, is really a list---and +that's guaranteed to be acceptable to {\tt map}. (This is an example of a +situation in which respecting a data abstraction would be too horrible to +contemplate.)} + +\subhd{More Robustness} + +The program we've shown you so far works fine, as long as the user +never makes a mistake. Because this program was written for absolute +novices, we wanted to bend over backward to catch any kind of strange input +they might give us. + +Using {\tt read} to accept user input has a number of disadvantages: + +\medskip +{\parindent=1em +\bb If the user enters an empty line, {\tt read} continues waiting silently +for input. +\bb If the user types an unmatched open parenthesis, {\tt read} continues +reading forever. +\bb If the user types two expressions on a line, the second one will be +taken as a response to the question the {\tt functions} program hasn't asked +yet. + +}\medskip + +\noindent We solve all these problems by using {\tt read-line} to read +exactly one line, even if it's empty or ill-formed, and then checking +explicitly for possible errors. + +{\tt Read-line} treats parentheses no differently from any other character. +That's an advantage if the user enters mismatched or inappropriately nested +parentheses. However, if the user correctly enters a sentence as an +argument to some function, {\tt read-line} will include the initial open +parenthesis as the first character of the first word, and the final close +parenthesis as the last character of the last word. {\tt Get-arg} must +correct for these extra characters. + +Similarly, {\tt read-line} treats number signs ({\tt #}) like any other +character, so it doesn't recognize {\tt #t} and {\tt #f} as special values. +Instead it reads them as the strings {\tt "#t"} and {\tt "#f"}. {\tt +Get-arg} calls {\tt booleanize} to convert those strings into Boolean values. + +{\prgex% +(define (\ufun{get-arg}) + (display "Argument: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type an argument!") + (get-arg)) + ((and (equal? "(" (first (first line))) + (equal? ")" (last (last line)))) + (let ((sent (remove-first-paren (remove-last-paren line)))) + (if (any-parens? sent) + (begin (show "Sentences can't have parentheses inside.") + (get-arg)) + (map booleanize sent)))) + ((any-parens? line) + (show "Bad parentheses") + (get-arg)) + ((empty? (bf line)) (booleanize (first line))) + (else (show "You typed more than one argument! Try again.") + (get-arg))))) +} + +\hyphenchar\tentt=\count123 +\noindent {\tt Get-arg} invokes {\tt any-parens?}, {\tt +remove-first-paren}, {\tt remove-last-paren}, and {\tt booleanize}, whose +meanings should be obvious from their names. You can look up their +definitions in the complete listing at the end of this chapter. + +\hyphenchar\tentt=-1 +{\tt Get-fn} is simpler than {\tt get-arg}, because there's no issue about +parentheses, but it's still much more complicated than the original version, +because of error checking. + +{\medskipamount=5pt + +{\prgex% +(define (\ufun{get-fn}) + (display "Function: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type a function!") + (get-fn)) + ((not (= (count line) 1)) + (show "You typed more than one thing! Try again.") + (get-fn)) + ((not (valid-fn-name? (first line))) + (show "Sorry, that's not a function.") + (get-fn)) + (else (first line))))) +} + +\noindent This version of {\tt get-fn} uses {\tt valid-fn-name?} (which +you've already seen) to ensure that what the user types is the name of a +function we know about. + +There's a problem with using {\tt read-line}. As we mentioned in a pitfall +in Chapter \io, reading some input with {\tt read} and then reading the next +input with {\tt read-line} results in {\tt read-line} returning an empty +line left over by {\tt read}. Although the {\tt functions} program doesn't +use {\tt read}, Scheme itself used {\tt read} to read the {\tt (functions)} +expression that started the program. Therefore, +{\tt get-fn}'s first attempt to read a function name will see an empty +line. To fix this problem, the {\tt functions} procedure has an +extra invocation of {\tt read-line}: + +{\prgex% +(define (functions) + (read-line) + (show "Welcome to the FUNCTIONS program.") + (functions-loop)) +} + +} % skip kludge + +\subhd{Complete Program Listing} + +\bigskip +\listing{functions.scm} + +\esubhd{Exercises} + +{\medskipamount=4pt +{\exercise +The {\tt get-args} procedure has a {\tt let} that creates the variable {\tt +first}, and then that variable is used only once inside the body of the {\tt +let}. Why doesn't it just say the following? + +{\prgex% +(define (get-args n) + (if (= n 0) + '() + (cons (get-arg) (get-args (- n 1))))) +} +} + +\solution + +The {\tt let} is needed to ensure that the +arguments in the returned list are in the correct order: + +Suppose that the user has chosen a two-argument function, so +{\tt functions-loop} calls {\tt get-args} with the argument 2. +{\tt Get-args} will ask +the user for two arguments; suppose that the user types ``the'' +as the first response and ``beatles'' as the second response. So {\tt +get-args} {\it should\/} return the list {\tt (the~beatles)}. + +Let's trace what happens, using the little people model. Glenda the {\tt +get-args} specialist is hired, with {\tt n} being 2. Since her {\tt n} +isn't 0, she evaluates the {\tt cons} expression. + +Assume that in this version of Scheme the arguments to a +procedure are evaluated from right to left. The first thing that happens +is that Glenda hires Gerry, another {\tt get-args} specialist, giving him +an argument of 1. (Later, Glenda will hire a {\tt get-arg} specialist, and +then a {\tt cons} specialist.) + +Gerry's job is to get one argument. We'll +take a leap of faith and assume that he correctly reads the first expression +the user typed, namely, the word {\tt the}. So Gerry returns the list {\tt +(the)}. + +Now Glenda hires a {\tt get-arg} specialist, who reads {\tt beatles} from +the user. Glenda then hires a {\tt cons} specialist to insert {\tt beatles} +onto the front of {\tt (the)}, giving {\tt (beatles~the)} as her final +answer. But this is wrong! Glenda's list is backwards; instead of +returning {\tt (the~beatles)} she returned {\tt (beatles~the)}. + +The {\tt let} in the correct version of {\tt get-args} is to ensure that the +call to {\tt read} happens {\it before\/} the recursive call, thus putting +the elements into the list in the right order. + +@ + +{\exercise +The domain-checking function for {\tt equal?} is + +{\prgex% +(lambda (x y) #t) +} + +\noindent This seems silly; it's a function of two arguments that ignores +both arguments and always returns {\tt \#t}. Since we know ahead of time +that the answer is {\tt \#t}, why won't it work to have {\tt equal?}'s entry +in the a-list be + +{\prgex% +(list 'equal? equal? 2 #t) +} +} +\solution +The {\tt functions} program uses the type-checking predicate by +invoking it with the given arguments. If the entry were {\tt #t} +instead of a function, then invoking it would be an error. +The relevant part of the program is {\tt in-domain?}: + +{\prgex% +(define (in-domain? args fn-name) + (apply (type-predicate fn-name) args)) +} + +\noindent If an entry had {\tt #t} as the type ``predicate,'' then +this would be equivalent to + +{\prgex% +(apply #t args) +} +@ +} %%% kludge + +{\exercise +Every time we want to know something about a function that the user typed +in, such as its number of arguments or its domain-checking predicate, we +have to do an {\tt assoc} in {\tt *the-functions*}. That's inefficient. +Instead, rewrite the program so that {\tt get-fn} returns a function's entry +from the a-list, instead of just its name. Then rename the variable {\tt +fn-name} to {\tt fn-entry} in the {\tt functions-loop} procedure, and rewrite +the selectors {\tt scheme-procedure}, {\tt arg-count}, and so on, so that +they don't invoke {\tt assoc}. +} + +\solution +The changed parts of the program are shown here in boldface. + +{\prgex% +(define (get-fn) + (display "Function: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type a function!") + (get-fn)) + ((not (= (count line) 1)) + (show "You typed more than one thing! Try again.") + (get-fn)) + ((not (valid-fn-name? (first line))) + (show "Sorry, that's not a function.") + (get-fn)) + (else \pmb{(assoc} (first line) \pmb{*the-functions)})))) + +(define (functions-loop) + (let ((\pmb{fn-entry} (get-fn))) + (if (equal? \pmb{(function-name fn-entry)} 'exit) + "Thanks for using FUNCTIONS!" + (let ((args (get-args (arg-count \pmb{fn-entry})))) + (if (not (in-domain? args \pmb{fn-entry})) + (show "Argument(s) not in domain.") + (show-answer (apply (scheme-function \pmb{fn-entry}) args))) + (functions-loop))))) + +(define (function-name \pmb{fn-entry}) + (car \pmb{fn-entry})) + +(define (scheme-function \pmb{fn-entry}) + (cadr \pmb{fn-entry})) + +(define (arg-count \pmb{fn-entry}) + (caddr \pmb{fn-entry})) + +(define (type-predicate \pmb{fn-entry}) + (cadddr \pmb{fn-entry})) +} + +A new selector {\tt function-name} was needed to look for the use +of the word {\tt exit} in place of a function name. +@ + +{\exercise +Currently, the program always gives the message ``argument(s) not in +domain'' when you try to apply a function to bad arguments. Modify the +program so that each record in {\tt *the-functions*} also contains a +specific out-of-domain message like ``both arguments must be numbers,'' then +modify {\tt functions} to look up and print this error message along with +``argument(s) not in domain.'' +} + +\solution +First we invent a new selector for the error message: + +{\prgex% +(define (domain-error-message fn-name) + (car (cddddr (assoc fn-name *the-functions*)))) +} + +\noindent We can't just say {\tt caddddr} because those functions +are only defined for up to four steps. For elements this far into +the list, it might be clearer to use {\tt list-ref}: + +{\prgex% +(define (domain-error-message fn-name) + (list-ref (assoc fn-name *the-functions*) 4)) +} + +Now we use the more specific error message. +The changed parts of the procedure are shown here in boldface. + +{\prgex% +(define (functions-loop) + (let ((fn-name (get-fn))) + (if (equal? fn-name 'exit) + "Thanks for using FUNCTIONS!" + (let ((args (get-args (arg-count fn-name)))) + (if (not (in-domain? args fn-name)) + \pmb{(begin} (show "Argument(s) not in domain:") + \pmb{(show (domain-error-message fn-name)))} + (show-answer (apply (scheme-function fn-name) args))) + (functions-loop))))) +} + +Of course we also must update the function list itself: + +{\prgex% +(define *the-functions* + (list (list '* * 2 two-numbers? "Both arguments must be numbers.") + \ellipsis + (list 'butfirst butfirst 1 not-empty? + "The argument must be a non-empty word or sentence.") + \ellipsis)) +} +@ + +\vfill\eject + +{\exercise +Modify the program so that it prompts for the arguments this way: + +{\prgex% +Function: if +First Argument: #t +Second Argument: paperback +Third Argument: writer + +The result is: PAPERBACK +} + +\noindent but if there's only one argument, the program shouldn't say {\tt +First}: + +{\prgex% +Function: sqrt +Argument: 36 + +The result is 6 +}} + +\solution + +The easiest way is not to modify {\tt get-arg} at all: + +{\prgex% +(define (get-args n) + (if (= n 1) + (list (get-arg)) + (get-args-helper 1 n))) + +(define (get-args-helper this total) + (if (> this total) + '() + (begin (display (ordinal this)) + (display " ") + (let ((first (get-arg))) + (cons first (get-args-helper (+ this 1) total)))))) + +(define (ordinal n) + (item n '("First" "Second" "Third" "Fourth" "Fifth"))) +} + +But it's a little bit of a kludge that {\tt get-args} prints half the prompt +and {\tt get-arg} prints the other half. Instead, we can have {\tt get-args} +figure out what the argument prompt should be, and tell {\tt get-arg} what +to print: + +{\prgex% +(define (get-args n) + (if (= n 1) + (list (get-arg "Argument: ")) + (get-args-helper 1 n))) + +(define (get-args-helper this total) + (if (> this total) + '() + (let ((first (get-arg (word (ordinal this) " Argument: ")))) + (cons first (get-args-helper (+ this 1) total))))) + +(define (get-arg prompt) + (display prompt) + ;;{\rm everything else get-arg used to do$\ldots$} + ) +} +@ + +{\exercise +The {\tt assoc} procedure might return {\tt \#f} instead of an a-list +record. How come it's okay for {\tt arg-count} to take the {\tt caddr} of +{\tt assoc}'s return value if {\tt (caddr~\#f)} is an error? +} + +\solution +{\tt Get-fn} invokes {\tt valid-fn-name?} to check whether or not +the requested function is in the a-list. If not, {\tt get-fn} asks +for another function name, so the selectors like {\tt arg-count} +will never be invoked with this nonexistent name. +@ + +{\exercise +Why is the domain-checking predicate for the {\tt word?} function + +{\prgex% +(lambda (x) #t) +} + +\noindent instead of the following procedure? + +{\prgex% +(lambda (x) (word? x)) +} +} + +\solution +The domain of the {\tt word?} function is {\it anything,} not just words. +Invoking the {\tt word?} predicate is like asking the question ``is this a +word?'' You can ask that question about anything you like: ``Is {\tt +(1~2~3)} a word?'' --- ``No.'' ``Is {\tt \#t} a word?'' --- ``No.'' ``Is +{\tt banana} a word?'' --- ``Yes.'' So {\it everything\/} is in the domain +of {\tt word?}, and its domain-checking predicate always returns {\tt #t}. + +Not every predicate has a domain that includes every possible value. +For example, the domain of the predicate {\tt even?} is just numbers; +you can't ask Scheme the question ``is {\tt banana} even?'' Therefore, +not every predicate will have + +{\prgex% +(lambda (x) #t) +} + +\noindent as its domain-checking function. But even for {\tt even?}, it +would be silly to use {\tt even?} itself as the domain-checking function. +If the domain of a predicate included only those things for which the +predicate is true, then that predicate could never return {\tt #f}! That +would make it a rather useless predicate. There's a difference between +asking Scheme a question to which it replies ``no'' and asking it a +meaningless question. + +% Many people have trouble with the difference between getting back the answer +% ``no'' to a question and asking a meaningless question. If I say ``Is 5 +% even?'' the answer is no. But if I ask ``Is {\tt elephant} even?'' it's a +% meaningless question. The domain of the {\tt even?} primitive is just +% numbers. On the other hand, the question ``Is such-and-such a word?'' is +% never meaningless, no matter what object I'm asking the question about. + +@ + +{\exercise +What is the value of the following Scheme expression? + +{\prgex% +(functions) +} +} + + +\solution + +Invoking the expression {\tt (functions)} causes a lot of activity to occur, +but the value of that expression is always the string {\tt "Thanks for using +FUNCTIONS!"} + +The {\tt functions} procedure has two (or three) expressions in its body, so +Scheme evaluates them all and returns the value of the last one. In this +case, the last one is a call to {\tt functions-loop}, so whatever {\tt +functions-loop} returns, {\tt functions} will return that, too. + +{\tt Functions-loop} is recursive. In the recursive case, there's a {\tt +let} with two expressions, so Scheme evaluates them both and returns the +value of the second one. The second expression is a recursive call to {\tt +functions-loop}, so whatever the recursive call returns is what the +outer call will also return. Thus, the string {\tt "Thanks for using +FUNCTIONS!"}, returned by the base case, will be the return value from every +single call to {\tt functions-loop}, including the first one, so it will +also be the return value from {\tt functions}. + +@ + +{\exercise +We said in the recursion chapters that every recursive procedure has to have +a base case and a recursive case, and that the recursive case has to somehow +reduce the size of the problem, getting closer to the base case. How does +the recursive call in {\tt get-fn} reduce the size of the problem? +} + +\solution + +Since {\tt get-fn} has no arguments, the recursive call is exactly the +same as the original call, so the problem doesn't get smaller. What +saves us is that even the original problem {\it might\/} be solved +without getting smaller at all, because it depends on the behavior of +the human user of the program. At each recursive invocation, the +problem is this: ``If the user enters a correct function, we're done. +If not, try again.'' If the user makes a mistake, then the procedure +calls itself recursively, with exactly the same problem. And, indeed, +this {\it can\/} be an infinite loop, if the user keeps typing incorrect +functions. But as soon as the user enters a correct function, the process +will terminate. + +% A call to {\tt get-fn} looks like {\tt (get-fn)}, because the procedure +% takes zero arguments. So the recursive call, {\tt (get-fn)} is exactly the +% same as the original call. There are two ways to think about this. +% +% The first is ``The size of the problem {\it doesn't\/} get smaller.'' Since +% each call to {\tt get-fn} has the same arguments, the problem never changes, +% and there's an infinite loop. Indeed, if you invoke {\tt get-fn} and then +% sit in front of the computer forever typing incorrect functions, the {\tt +% get-fn} procedure will never return a value. +% +% The second way to think about this is ``even though the arguments never +% change, the state of the world changes between recursive calls, so +% eventually the program will return a value.'' In this case, the progress +% made by each recursive call is sort of bizarre: after each time the user +% incorrectly types the name of a function, he or she is more likely to get it +% right the next time. + +@ +\bye diff --git a/js/games/nluqo.github.io/~bh/ssch21/functions-implement.html b/js/games/nluqo.github.io/~bh/ssch21/functions-implement.html new file mode 100644 index 0000000..ce3c1b9 --- /dev/null +++ b/js/games/nluqo.github.io/~bh/ssch21/functions-implement.html @@ -0,0 +1,939 @@ +<P> +<A NAME="oz"></A> +<P><CENTER><IMG SRC="../ss-pics/oz.jpg" ALT="figure: oz"></CENTER><P><CENTER>Once you see how it works, it's not so mysterious. +</CENTER><P> + + +<P><HTML> +<HEAD> +<TITLE>Simply Scheme: Introducing Computer Science ch 21: Example: The Functions Program</TITLE> +</HEAD> +<BODY> +<HR> +<CITE>Simply Scheme:</CITE> +<CITE>Introducing Computer Science</CITE> 2/e Copyright (C) 1999 MIT +<H2>Chapter 21</H2> +<H1>Example: The Functions Program</H1> + +<TABLE width="100%"><TR><TD> +<IMG SRC="../simply.jpg" ALT="cover photo"> +<TD><TABLE> +<TR><TD align="right"><CITE><A HREF="http://www.cs.berkeley.edu/~bh/">Brian +Harvey</A><BR>University of California, Berkeley</CITE> +<TR><TD align="right"><CITE><A HREF="http://ccrma.stanford.edu/~matt">Matthew +Wright</A><BR>University of California, Santa Barbara</CITE> +<TR><TD align="right"><BR> +<TR><TD align="right"><A HREF="../pdf/ssch21.pdf">Download PDF version</A> +<TR><TD align="right"><A HREF="../ss-toc2.html">Back to Table of Contents</A> +<TR><TD align="right"><A HREF="../ssch20/io.html"><STRONG>BACK</STRONG></A> +chapter thread <A HREF="../ssch22/files.html"><STRONG>NEXT</STRONG></A> +<TR><TD align="right"><A HREF="http://mitpress.mit.edu/0262082810">MIT +Press web page for <CITE>Simply Scheme</CITE></A> +</TABLE></TABLE> + +<HR> + + +In Chapter 2 you used a program called <A NAME="g1"></A><CODE>functions</CODE> to explore +some of Scheme's primitive functions. Now we're going to go back to that +program from the other point of view: instead of using the program to learn +about functions, we're going to look at how the program works as an example +of programming with input and output. + +<P><H2>The Main Loop</H2> + +<P>The <CODE>functions</CODE> program is an infinite loop similar to Scheme's +read-eval-print loop. It reads in a function name and some arguments, +prints the result of applying that function to those arguments, and then +does the whole thing over again. + +<P>There are some complexities, though. The <CODE>functions</CODE> program keeps +asking you for arguments until it has enough. This means that the <CODE>read</CODE> portion of the loop has to read a function name, figure out how many +arguments that procedure takes, and then ask for the right number of +arguments. On the other hand, each argument is an implicitly quoted datum +rather than an expression to be evaluated; the <CODE>functions</CODE> evaluator +avoids the recursive complexity of arbitrary subexpressions within +expressions. (That's why we wrote it: to focus attention on one function +invocation at a time, rather than on the composition of functions.) +Here's the main loop: + +<P><PRE>(define (<A NAME="g2"></A>functions-loop) + (let ((fn-name (get-fn))) + (if (equal? fn-name 'exit) + "Thanks for using FUNCTIONS!" + (let ((args (get-args (arg-count fn-name)))) + (if (not (in-domain? args fn-name)) + (show "Argument(s) not in domain.") + (show-answer (apply (scheme-procedure fn-name) args))) + (functions-loop))))) +</PRE> + +<P>This invokes a lot of helper procedures. <CODE>Arg-count</CODE> takes +the name of a procedure as its argument and returns the number of arguments +that the given procedure takes. <CODE>In-domain?</CODE> takes a list and the name +of a procedure as arguments; it returns <CODE>#t</CODE> if the elements of the list +are valid arguments to the given procedure. <CODE>Scheme-procedure</CODE> takes a +name as its argument and returns the Scheme procedure with the given name. +We'll get back to these helpers later. + +<P>The other helper procedures are the ones that do the input and output. The +actual versions are more complicated because of error checking; we'll show +them to you later. + +<P><PRE>(define (get-fn) ;; first version + (display "Function: ") + (read)) + +(define (<A NAME="g3"></A>get-args n) + (if (= n 0) + '() + (let ((first (get-arg))) + (cons first (get-args (- n 1)))))) + +(define (get-arg) ;; first version + (display "Argument: ") + (read)) + +(define (<A NAME="g4"></A>show-answer answer) + (newline) + (display "The result is: ") + (if (not answer) + (show "#F") + (show answer)) + (newline)) +</PRE> + +<P>(That weird <CODE>if</CODE> expression in <CODE>show-answer</CODE> is needed +because in some old versions of Scheme the empty list means the same as <CODE>#f</CODE>. We wanted to avoid raising this issue in Chapter 2, so we +just made sure that false values always printed as <CODE>#F</CODE>.) + +<P><H2>The Difference between a Procedure and Its Name</H2> + +<P>You may be wondering why we didn't just say + +<P><PRE>(show-answer (apply fn-name args)) +</PRE> + +<P>in the definition of <CODE>functions-loop</CODE>. Remember that the value of +the variable <CODE>fn-name</CODE> comes from <CODE>get-fn</CODE>, which invokes <CODE>read</CODE>. +Suppose you said + +<P> +<PRE>(define x (read)) +</PRE> + +<P>and then typed + +<P><PRE>(+ 2 3) +</PRE> + +<P>The value of <CODE>x</CODE> would be the three element list <CODE>(+ 2 3)</CODE>, not the number five. + +<P>Similarly, if you type "butfirst," then read will return the <EM>word</EM> <CODE>butfirst</CODE>, not the procedure of that name. So we need a way to +turn the name of a function into the procedure itself. + +<P><H2>The Association List of Functions</H2> + +<P>We accomplish this by creating a huge association list that contains +all of the functions the program knows about. Given a word, such as +<CODE>butfirst</CODE>, we need to know three things: + +<P><P><TABLE><TR><TH align="right" valign="top">•<TD> <TD valign="top">The Scheme procedure with that name (in this case, the <CODE>butfirst</CODE> +procedure). +</TABLE><TABLE><TR><TH align="right" valign="top">•<TD> <TD valign="top">The number of arguments required by the given procedure +(one).<A NAME="text1" HREF="functions-implement.html#ft1">[1]</A> +</TABLE><TABLE><TR><TH align="right" valign="top">•<TD> <TD valign="top">The types of arguments required by the given procedure (one word or +sentence, which must not be empty). + +</TABLE><P> +We need to know the number of arguments the procedure requires because the +program prompts the user individually for each argument; it has to know how +many to ask for. Also, it needs to know the domain of each function so +it can complain if the arguments you give it are not in the +domain.<A NAME="text2" HREF="functions-implement.html#ft2">[2]</A> + +<P>This means that each entry in the association list is a list of four +elements: + +<P><PRE>(define *the-functions* ;; partial listing + (list (list '* * 2 (lambda (x y) (and (number? x) (number? y)))) + (list '+ + 2 (lambda (x y) (and (number? x) (number? y)))) + (list 'and (lambda (x y) (and x y)) 2 + (lambda (x y) (and (boolean? x) (boolean? y)))) + (list 'equal? equal? 2 (lambda (x y) #t)) + (list 'even? even? 1 integer?) + (list 'word word 2 (lambda (x y) (and (word? x) (word? y)))))) +</PRE> + +<P>The real list is much longer, of course, but you get the +idea.<A NAME="text3" HREF="functions-implement.html#ft3">[3]</A> It's a +convention in Scheme programming that names of global variables used +throughout a program are surrounded by <CODE>*</CODE>asterisks<CODE>*</CODE> to +distinguish them from parameters of procedures. + +<P>Here are the selector procedures for looking up information in this a-list: + +<P><PRE>(define (<A NAME="g5"></A>scheme-procedure fn-name) + (cadr (assoc fn-name *the-functions*))) + +(define (<A NAME="g6"></A>arg-count fn-name) + (caddr (assoc fn-name *the-functions*))) + +(define (<A NAME="g7"></A>type-predicate fn-name) + (cadddr (assoc fn-name *the-functions*))) +</PRE> + +<P><H2>Domain Checking</H2> + +<P> +Note that we represent the domain of a procedure by another +procedure.<A NAME="text4" HREF="functions-implement.html#ft4">[4]</A> Each domain-checking procedure, or <EM>type +predicate,</EM> takes the same arguments as the procedure whose domain it +checks. For example, the type predicate for <CODE>+</CODE> is + +<P><PRE>(lambda (x y) (and (number? x) (number? y))) +</PRE> + +<P>The type predicate returns <CODE>#t</CODE> if its arguments +are valid and <CODE>#f</CODE> otherwise. So in the case of <CODE>+</CODE>, any two +numbers are valid inputs, but any other types of arguments aren't. + +<P>Here's the <CODE>in-domain?</CODE> predicate: + +<P><PRE>(define (<A NAME="g8"></A>in-domain? args fn-name) + (apply (type-predicate fn-name) args)) +</PRE> + +<P>Of course, certain type predicates are applicable to more than one +procedure. It would be silly to type + +<P><PRE>(lambda (x y) (and (number? x) (number? y))) +</PRE> + +<P>for <CODE>+</CODE>, <CODE>-</CODE>, <CODE>=</CODE>, and so on. Instead, we give this +function a name: + +<P><PRE>(define (<A NAME="g9"></A>two-numbers? x y) + (and (number? x) (number? y))) +</PRE> + +<P>We then refer to the type predicate by name in the a-list: + +<P><PRE>(define *the-functions* ;; partial listing, revised + (list (list '* * 2 two-numbers?) + (list '+ + 2 two-numbers?) + (list 'and (lambda (x y) (and x y)) 2 + (lambda (x y) (and (boolean? x) (boolean? y)))) + (list 'equal? equal? 2 (lambda (x y) #t)) + (list 'even? even? 1 integer?) + (list 'word word 2 (lambda (x y) (and (word? x) (word? y)))))) +</PRE> + +<P>Some of the type predicates are more complicated. For example, +here's the one for the <CODE>member?</CODE> and <CODE>appearances</CODE> functions: + +<P><PRE>(define (<A NAME="g10"></A>member-types-ok? small big) + (and (word? small) + (or (sentence? big) (and (word? big) (= (count small) 1))))) +</PRE> + +<P><CODE>Item</CODE> also has a complicated domain: + +<P><PRE>(lambda (n stuff) + (and (integer? n) (> n 0) + (word-or-sent? stuff) (<= n (count stuff)))) +</PRE> + +<P>This invokes <CODE>word-or-sent?</CODE>, which is itself the type +predicate for the <CODE>count</CODE> procedure: + +<P><PRE>(define (word-or-sent? x) + (or (word? x) (sentence? x))) +</PRE> + +<P>On the other hand, some are less complicated. <CODE>Equal?</CODE> will accept any +two arguments, so its type predicate is just + +<P><PRE>(lambda (x y) #t) +</PRE> + +<P>The complete listing at the end of the chapter shows the details of all these +procedures. Note that the <CODE>functions</CODE> program has a more restricted +idea of domain than Scheme does. For example, in Scheme + +<P><PRE>(and 6 #t) +</PRE> + +<P>returns <CODE>#t</CODE> and does not generate an error. But in +the <CODE>functions</CODE> program the argument <CODE>6</CODE> is considered out of the +domain.<A NAME="text5" HREF="functions-implement.html#ft5">[5]</A> + +<P>If you don't like math, just ignore the domain predicates for the +mathematical primitives; they involve facts about the domains of math +functions that we don't expect you to know.<A NAME="text6" HREF="functions-implement.html#ft6">[6]</A> + +<P><H2>Intentionally Confusing a Function with Its Name</H2> + +<P> +Earlier we made a big deal about the difference between a procedure and its +name, to make sure you wouldn't think you can apply the <EM>word</EM> <CODE>butfirst</CODE> to arguments. But the <CODE>functions</CODE> program completely hides +this distinction from the user: + +<P><PRE>Function: count +Argument: butlast + +The result is: 7 + +Function: every +Argument: butlast +Argument: (helter skelter) + +The result is: (HELTE SKELTE) +</PRE> + +<P>When we give <CODE>butlast</CODE> as an argument to <CODE>count</CODE>, it's as if we'd said + +<P><PRE>(count 'butlast) +</PRE> + +<P>In other words, it's taken as a word. But when we give <CODE>butlast</CODE> as an argument to <CODE>every</CODE>, it's as if we'd said + +<P><PRE>(every butlast '(helter skelter)) +</PRE> + +<P>How can we treat some arguments as quoted and others not? The way this +works is that <EM>everything</EM> is considered a word or a sentence by the +<CODE>functions</CODE> program. The higher-order functions <CODE>every</CODE> and <CODE>keep</CODE> are actually represented in the <CODE>functions</CODE> implementation by +Scheme procedures that take the <EM>name</EM> of a function as an argument, +instead of a procedure itself as the ordinary versions do: + +<P><PRE>(define (<A NAME="g11"></A>named-every fn-name list) + (every (scheme-procedure fn-name) list)) + +(define (<A NAME="g12"></A>named-keep fn-name list) + (keep (scheme-procedure fn-name) list)) + +> (every first '(another girl)) +(A G) +> (named-every 'first '(another girl)) +(A G) +> (every 'first '(another girl)) +ERROR: ATTEMPT TO APPLY NON-PROCEDURE FIRST +</PRE> + +<P> +This illustration hides a subtle point. When we invoked <CODE>named-every</CODE> at a Scheme prompt, we had to quote the word <CODE>first</CODE> +that we used as its argument. But when you run the <CODE>functions</CODE> program, +you don't quote anything. The point is that <CODE>functions</CODE> provides an +evaluator that uses a different notation from Scheme's notation. It may be +clearer if we show an interaction with an imaginary version of <CODE>functions</CODE> that <EM>does</EM> use Scheme notation: + +<P><PRE>Function: first +Non-Automatically-Quoted-Argument: 'datum + +The result is: D + +Function: first +Non-Automatically-Quoted-Argument: datum + +ERROR: THE VARIABLE DATUM IS UNBOUND. +</PRE> + +<P>We didn't want to raise the issue of quoting at that early point +in the book, so we wrote <CODE>functions</CODE> so that <EM>every</EM> argument +is automatically quoted. Well, if that's the case, it's true even when +we're invoking <CODE>every</CODE>. If you say + +<P><PRE>Function: every +Argument: first +…</PRE> + +<P>then by the rules of the <CODE>functions</CODE> program, that argument is +the quoted word <CODE>first</CODE>. So <CODE>named-every</CODE>, the procedure that +pretends to be <CODE>every</CODE> in the <CODE>functions</CODE> world, has to "un-quote" +that argument by looking up the corresponding procedure. + +<P><H2>More on Higher-Order Functions</H2> + +<P>One of the higher-order functions that you can use in the <CODE>functions</CODE> +program is called <CODE>number-of-arguments</CODE>. It takes a procedure (actually +the name of a procedure, as we've just been saying) as argument and returns +the number of arguments that that procedure accepts. This example is +unusual because there's no such function in Scheme. (It would be +complicated to define, for one thing, because some Scheme procedures can +accept a variable number of arguments. What should <CODE>number-of-arguments</CODE> return for such a procedure?) + +<P>The implementation of <CODE>number-of-arguments</CODE> makes use of the same a-list +of functions that the <CODE>functions</CODE> evaluator itself uses. Since the +<CODE>functions</CODE> program needs to know the number of arguments for every +procedure anyway, it's hardly any extra effort to make that information +available to the user. We just add an entry to the a-list: + +<P><PRE>(list '<A NAME="g13"></A>number-of-arguments arg-count 1 valid-fn-name?) +</PRE> + +<P>The type predicate merely has to check that the argument +is found in the a-list of functions: + +<P><PRE>(define (<A NAME="g14"></A>valid-fn-name? name) + (assoc name *the-functions*)) +</PRE> + +<P>The type checking for the arguments to <CODE>every</CODE> and <CODE>keep</CODE> is +unusually complicated because what's allowed as the second argument (the +collection of data) depends on which function is used as the first argument. +For example, it's illegal to compute + +<P><PRE>(every square '(think for yourself)) +</PRE> + +<P>even though either of those two arguments would be allowable if we +changed the other one: + +<P><PRE>> (every square '(3 4 5)) +(9 16 25) + +> (every first '(think for yourself)) +(T F Y) +</PRE> + +<P>The type-checking procedures for <CODE>every</CODE> and <CODE>keep</CODE> use a common +subprocedure. The one for <CODE>every</CODE> is + +<P><PRE>(lambda (fn stuff) + (hof-types-ok? fn stuff word-or-sent?)) +</PRE> + +<P>and the one for <CODE>keep</CODE> is + +<P><PRE>(lambda (fn stuff) + (hof-types-ok? fn stuff boolean?)) +</PRE> + +<P>The third argument specifies what types of results <CODE>fn</CODE> must +return when applied to the elements of <CODE>stuff</CODE>. + +<P><PRE>(define (hof-types-ok? fn-name stuff range-predicate) + (and (valid-fn-name? fn-name) + (= 1 (arg-count fn-name)) + (word-or-sent? stuff) + (empty? (keep (lambda (element) + (not ((type-predicate fn-name) element))) + stuff)) + (null? (filter (lambda (element) + (not (range-predicate element))) + (map (scheme-procedure fn-name) + (every (lambda (x) x) stuff)))))) +</PRE> + +<P>This says that the function being used as the first argument must +be a one-argument function (so you can't say, for example, <CODE>every</CODE> of +<CODE>word</CODE> and something); also, <EM>each element</EM> of the second argument +must be an acceptable argument to that function. (If you <CODE>keep</CODE> the +unacceptable arguments, you get nothing.) Finally, each invocation of the +given function on an element of <CODE>stuff</CODE> must return an object of the +appropriate type: words or sentences for <CODE>every</CODE>, true or false for +<CODE>keep</CODE>.<A NAME="text7" HREF="functions-implement.html#ft7">[7]</A> + +<P><H2>More Robustness</H2> + +<P>The program we've shown you so far works fine, as long as the user +never makes a mistake. Because this program was written for absolute +novices, we wanted to bend over backward to catch any kind of strange input +they might give us. + +<P>Using <CODE>read</CODE> to accept user input has a number of disadvantages: + +<P><P><TABLE><TR><TH align="right" valign="top">•<TD> <TD valign="top">If the user enters an empty line, <CODE>read</CODE> continues waiting silently +for input. +</TABLE><TABLE><TR><TH align="right" valign="top">•<TD> <TD valign="top">If the user types an unmatched open parenthesis, <CODE>read</CODE> continues +reading forever. +</TABLE><TABLE><TR><TH align="right" valign="top">•<TD> <TD valign="top">If the user types two expressions on a line, the second one will be +taken as a response to the question the <CODE>functions</CODE> program hasn't asked +yet. + +</TABLE><P> +We solve all these problems by using <CODE>read-line</CODE> to read +exactly one line, even if it's empty or ill-formed, and then checking +explicitly for possible errors. + +<P><CODE>Read-line</CODE> treats parentheses no differently from any other character. +That's an advantage if the user enters mismatched or inappropriately nested +parentheses. However, if the user correctly enters a sentence as an +argument to some function, <CODE>read-line</CODE> will include the initial open +parenthesis as the first character of the first word, and the final close +parenthesis as the last character of the last word. <CODE>Get-arg</CODE> must +correct for these extra characters. + +<P>Similarly, <CODE>read-line</CODE> treats number signs (<CODE>#</CODE>) like any other +character, so it doesn't recognize <CODE>#t</CODE> and <CODE>#f</CODE> as special values. +Instead it reads them as the strings <CODE>"#t"</CODE> and <CODE>"#f"</CODE>. <CODE>Get-arg</CODE> calls <CODE>booleanize</CODE> to convert those strings into Boolean values. + +<P><PRE>(define (<A NAME="g15"></A>get-arg) + (display "Argument: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type an argument!") + (get-arg)) + ((and (equal? "(" (first (first line))) + (equal? ")" (last (last line)))) + (let ((sent (remove-first-paren (remove-last-paren line)))) + (if (any-parens? sent) + (begin (show "Sentences can't have parentheses inside.") + (get-arg)) + (map booleanize sent)))) + ((any-parens? line) + (show "Bad parentheses") + (get-arg)) + ((empty? (bf line)) (booleanize (first line))) + (else (show "You typed more than one argument! Try again.") + (get-arg))))) +</PRE> + +<P><CODE>Get-arg</CODE> invokes <CODE>any-parens?</CODE>, <CODE>remove-first-paren</CODE>, <CODE>remove-last-paren</CODE>, and <CODE>booleanize</CODE>, whose +meanings should be obvious from their names. You can look up their +definitions in the complete listing at the end of this chapter. + +<P><CODE>Get-fn</CODE> is simpler than <CODE>get-arg</CODE>, because there's no issue about +parentheses, but it's still much more complicated than the original version, +because of error checking. + +<P> +<PRE>(define (<A NAME="g16"></A>get-fn) + (display "Function: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type a function!") + (get-fn)) + ((not (= (count line) 1)) + (show "You typed more than one thing! Try again.") + (get-fn)) + ((not (valid-fn-name? (first line))) + (show "Sorry, that's not a function.") + (get-fn)) + (else (first line))))) +</PRE> + +<P>This version of <CODE>get-fn</CODE> uses <CODE>valid-fn-name?</CODE> (which +you've already seen) to ensure that what the user types is the name of a +function we know about. + +<P>There's a problem with using <CODE>read-line</CODE>. As we mentioned in a pitfall +in Chapter 20, reading some input with <CODE>read</CODE> and then reading the next +input with <CODE>read-line</CODE> results in <CODE>read-line</CODE> returning an empty +line left over by <CODE>read</CODE>. Although the <CODE>functions</CODE> program doesn't +use <CODE>read</CODE>, Scheme itself used <CODE>read</CODE> to read the <CODE>(functions)</CODE> +expression that started the program. Therefore, +<CODE>get-fn</CODE>'s first attempt to read a function name will see an empty +line. To fix this problem, the <CODE>functions</CODE> procedure has an +extra invocation of <CODE>read-line</CODE>: + +<P><PRE>(define (functions) + (read-line) + (show "Welcome to the FUNCTIONS program.") + (functions-loop)) +</PRE> + +<P> +<H2>Complete Program Listing</H2> + +<P><P><P><PRE> +;;; The functions program + +(define (functions) + ;; (read-line) + (show "Welcome to the FUNCTIONS program.") + (functions-loop)) + +(define (functions-loop) + (let ((fn-name (get-fn))) + (if (equal? fn-name 'exit) + "Thanks for using FUNCTIONS!" + (let ((args (get-args (arg-count fn-name)))) + (if (not (in-domain? args fn-name)) + (show "Argument(s) not in domain.") + (show-answer (apply (scheme-function fn-name) args))) + (functions-loop))))) + +(define (get-fn) + (display "Function: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type a function!") + (get-fn)) + ((not (= (count line) 1)) + (show "You typed more than one thing! Try again.") + (get-fn)) + ((not (valid-fn-name? (first line))) + (show "Sorry, that's not a function.") + (get-fn)) + (else (first line))))) + +(define (get-arg) + (display "Argument: ") + (let ((line (read-line))) + (cond ((empty? line) + (show "Please type an argument!") + (get-arg)) + ((and (equal? "(" (first (first line))) + (equal? ")" (last (last line)))) + (let ((sent (remove-first-paren (remove-last-paren line)))) + (if (any-parens? sent) + (begin + (show "Sentences can't have parentheses inside.") + (get-arg)) + (map booleanize sent)))) + ((any-parens? line) + (show "Bad parentheses") + (get-arg)) + ((empty? (bf line)) (booleanize (first line))) + (else (show "You typed more than one argument! Try again.") + (get-arg))))) + +(define (get-args n) + (if (= n 0) + '() + (let ((first (get-arg))) + (cons first (get-args (- n 1)))))) + +(define (any-parens? line) + (let ((letters (accumulate word line))) + (or (member? "(" letters) + (member? ")" letters)))) + +(define (remove-first-paren line) + (if (equal? (first line) "(") + (bf line) + (se (bf (first line)) (bf line)))) + +(define (remove-last-paren line) + (if (equal? (last line) ")") + (bl line) + (se (bl line) (bl (last line))))) + +(define (booleanize x) + (cond ((equal? x "#t") #t) + ((equal? x "#f") #f) + (else x))) + +(define (show-answer answer) + (newline) + (display "The result is: ") + (if (not answer) + (show "#F") + (show answer)) + (newline)) + +(define (scheme-function fn-name) + (cadr (assoc fn-name *the-functions*))) + +(define (arg-count fn-name) + (caddr (assoc fn-name *the-functions*))) + +(define (type-predicate fn-name) + (cadddr (assoc fn-name *the-functions*))) + +(define (in-domain? args fn-name) + (apply (type-predicate fn-name) args)) + + +;; Type predicates + +(define (word-or-sent? x) + (or (word? x) (sentence? x))) + +(define (not-empty? x) + (and (word-or-sent? x) (not (empty? x)))) + +(define (two-numbers? x y) + (and (number? x) (number? y))) + +(define (two-reals? x y) + (and (real? x) (real? y))) + +(define (two-integers? x y) + (and (integer? x) (integer? y))) + +(define (can-divide? x y) + (and (number? x) (number? y) (not (= y 0)))) + +(define (dividable-integers? x y) + (and (two-integers? x y) (not (= y 0)))) + +(define (trig-range? x) + (and (number? x) (<= (abs x) 1))) + +(define (hof-types-ok? fn-name stuff range-predicate) + (and (valid-fn-name? fn-name) + (= 1 (arg-count fn-name)) + (word-or-sent? stuff) + (empty? (keep (lambda (element) + (not ((type-predicate fn-name) element))) + stuff)) + (null? (filter (lambda (element) + (not (range-predicate element))) + (map (scheme-function fn-name) + (every (lambda (x) x) stuff)))))) + +(define (member-types-ok? small big) + (and (word? small) + (or (sentence? big) (and (word? big) (= (count small) 1))))) + + +;; Names of functions as functions + +(define (named-every fn-name list) + (every (scheme-function fn-name) list)) + +(define (named-keep fn-name list) + (keep (scheme-function fn-name) list)) + +(define (valid-fn-name? name) + (assoc name *the-functions*)) + +;; The list itself + +(define *the-functions* + (list (list '* * 2 two-numbers?) + (list '+ + 2 two-numbers?) + (list '- - 2 two-numbers?) + (list '/ / 2 can-divide?) + (list '< < 2 two-reals?) + (list '<= <= 2 two-reals?) + (list '= = 2 two-numbers?) + (list '> > 2 two-reals?) + (list '>= >= 2 two-reals?) + (list 'abs abs 1 real?) + (list 'acos acos 1 trig-range?) + (list 'and (lambda (x y) (and x y)) 2 + (lambda (x y) (and (boolean? x) (boolean? y)))) + (list 'appearances appearances 2 member-types-ok?) + (list 'asin asin 1 trig-range?) + (list 'atan atan 1 number?) + (list 'bf bf 1 not-empty?) + (list 'bl bl 1 not-empty?) + (list 'butfirst butfirst 1 not-empty?) + (list 'butlast butlast 1 not-empty?) + (list 'ceiling ceiling 1 real?) + (list 'cos cos 1 number?) + (list 'count count 1 word-or-sent?) + (list 'equal? equal? 2 (lambda (x y) #t)) + (list 'even? even? 1 integer?) + (list 'every named-every 2 + (lambda (fn stuff) + (hof-types-ok? fn stuff word-or-sent?))) + (list 'exit '() 0 '()) + ; in case user applies number-of-arguments to exit + (list 'exp exp 1 number?) + + (list 'expt expt 2 + (lambda (x y) + (and (number? x) (number? y) + (or (not (real? x)) (>= x 0) (integer? y))))) + (list 'first first 1 not-empty?) + (list 'floor floor 1 real?) + (list 'gcd gcd 2 two-integers?) + (list 'if (lambda (pred yes no) (if pred yes no)) 3 + (lambda (pred yes no) (boolean? pred))) + (list 'item item 2 + (lambda (n stuff) + (and (integer? n) (> n 0) + (word-or-sent? stuff) (<= n (count stuff))))) + (list 'keep named-keep 2 + (lambda (fn stuff) + (hof-types-ok? fn stuff boolean?))) + (list 'last last 1 not-empty?) + (list 'lcm lcm 2 two-integers?) + (list 'log log 1 (lambda (x) (and (number? x) (not (= x 0))))) + (list 'max max 2 two-reals?) + (list 'member? member? 2 member-types-ok?) + (list 'min min 2 two-reals?) + (list 'modulo modulo 2 dividable-integers?) + (list 'not not 1 boolean?) + (list 'number-of-arguments arg-count 1 valid-fn-name?) + (list 'odd? odd? 1 integer?) + (list 'or (lambda (x y) (or x y)) 2 + (lambda (x y) (and (boolean? x) (boolean? y)))) + (list 'quotient quotient 2 dividable-integers?) + (list 'random random 1 (lambda (x) (and (integer? x) (> x 0)))) + (list 'remainder remainder 2 dividable-integers?) + (list 'round round 1 real?) + (list 'se se 2 + (lambda (x y) (and (word-or-sent? x) (word-or-sent? y)))) + (list 'sentence sentence 2 + (lambda (x y) (and (word-or-sent? x) (word-or-sent? y)))) + (list 'sentence? sentence? 1 (lambda (x) #t)) + (list 'sin sin 1 number?) + (list 'sqrt sqrt 1 (lambda (x) (and (real? x) (>= x 0)))) + (list 'tan tan 1 number?) + (list 'truncate truncate 1 real?) + (list 'vowel? (lambda (x) (member? x '(a e i o u))) 1 + (lambda (x) #t)) + (list 'word word 2 (lambda (x y) (and (word? x) (word? y)))) + (list 'word? word? 1 (lambda (x) #t)))) + +</PRE><P> + + +<P><H2>Exercises</H2> + +<P><B>21.1</B> The <CODE>get-args</CODE> procedure has a <CODE>let</CODE> that creates the variable <CODE>first</CODE>, and then that variable is used only once inside the body of the <CODE>let</CODE>. Why doesn't it just say the following? + +<P><PRE>(define (get-args n) + (if (= n 0) + '() + (cons (get-arg) (get-args (- n 1))))) +</PRE> + + +<P> +<B>21.2</B> The domain-checking function for <CODE>equal?</CODE> is + +<P><PRE>(lambda (x y) #t) +</PRE> + +<P>This seems silly; it's a function of two arguments that ignores +both arguments and always returns <CODE>#t</CODE>. Since we know ahead of time +that the answer is <CODE>#t</CODE>, why won't it work to have <CODE>equal?</CODE>'s entry +in the a-list be + +<P><PRE>(list 'equal? equal? 2 #t) +</PRE> + + +<B>21.3</B> Every time we want to know something about a function that the user typed +in, such as its number of arguments or its domain-checking predicate, we +have to do an <CODE>assoc</CODE> in <CODE>*the-functions*</CODE>. That's inefficient. +Instead, rewrite the program so that <CODE>get-fn</CODE> returns a function's entry +from the a-list, instead of just its name. Then rename the variable <CODE>fn-name</CODE> to <CODE>fn-entry</CODE> in the <CODE>functions-loop</CODE> procedure, and rewrite +the selectors <CODE>scheme-procedure</CODE>, <CODE>arg-count</CODE>, and so on, so that +they don't invoke <CODE>assoc</CODE>. + + +<P> +<B>21.4</B> Currently, the program always gives the message "argument(s) not in +domain" when you try to apply a function to bad arguments. Modify the +program so that each record in <CODE>*the-functions*</CODE> also contains a +specific out-of-domain message like "both arguments must be numbers," then +modify <CODE>functions</CODE> to look up and print this error message along with +"argument(s) not in domain." + + +<P> + +<B>21.5</B> Modify the program so that it prompts for the arguments this way: + +<P><PRE>Function: if +First Argument: #t +Second Argument: paperback +Third Argument: writer + +The result is: PAPERBACK +</PRE> + +<P>but if there's only one argument, the program shouldn't say <CODE>First</CODE>: + +<P><PRE>Function: sqrt +Argument: 36 + +The result is 6 +</PRE> + +<P> +<B>21.6</B> The <CODE>assoc</CODE> procedure might return <CODE>#f</CODE> instead of an a-list +record. How come it's okay for <CODE>arg-count</CODE> to take the <CODE>caddr</CODE> of +<CODE>assoc</CODE>'s return value if <CODE>(caddr #f)</CODE> is an error? + + +<P> +<B>21.7</B> Why is the domain-checking predicate for the <CODE>word?</CODE> function + +<P><PRE>(lambda (x) #t) +</PRE> + +<P>instead of the following procedure? + +<P><PRE>(lambda (x) (word? x)) +</PRE> + + +<P> +<B>21.8</B> What is the value of the following Scheme expression? + +<P><PRE>(functions) +</PRE> + + +<P> +<P> +<B>21.9</B> We said in the recursion chapters that every recursive procedure has to have +a base case and a recursive case, and that the recursive case has to somehow +reduce the size of the problem, getting closer to the base case. How does +the recursive call in <CODE>get-fn</CODE> reduce the size of the problem? + + +<P> +<HR> +<A NAME="ft1" HREF="functions-implement.html#text1">[1]</A> Some Scheme procedures can accept any number of arguments, +but for the purposes of the <CODE>functions</CODE> program we restrict these +procedures to their most usual case, such as two arguments for <CODE>+</CODE>.<P> +<A NAME="ft2" HREF="functions-implement.html#text2">[2]</A> Scheme would complain all by itself, of course, but would +then stop running the <CODE>functions</CODE> program. We want to catch the error +before Scheme does, so that after seeing the error message you're still in +<CODE>functions</CODE>. As we mentioned in Chapter 19, a program meant for +beginners, such as the readers of Chapter 2, should be +especially robust.<P> +<A NAME="ft3" HREF="functions-implement.html#text3">[3]</A> Since <CODE>and</CODE> is a special form, we can't just say + +<P><PRE>(list 'and and 2 (lambda (x y) (and (boolean? x) (boolean? y)))) +</PRE> + +<P>That's because special forms can't be elements of lists. Instead, we have +to create a normal procedure that can be put in a list but computes the same +function as <CODE>and</CODE>: + +<P><PRE>(lambda (x y) (and x y)) +</PRE> + +<P>We can get away with this because in the <CODE>functions</CODE> program we +don't care about argument evaluation, so it doesn't matter that <CODE>and</CODE> is +a special form. We do the same thing for <CODE>if</CODE> and <CODE>or</CODE>.<P> +<A NAME="ft4" HREF="functions-implement.html#text4">[4]</A> The domain of a procedure is a set, and sets are generally +represented in programs as lists. You might think that we'd have to store, +for example, a list of all the legal arguments to <CODE>butfirst</CODE>. But that +would be impossible, since that list would have to be infinitely large. +Instead, we can take advantage of the fact that the only use we make of this +set is membership testing, that is, finding out whether a particular argument +is in a function's domain.<P> +<A NAME="ft5" HREF="functions-implement.html#text5">[5]</A> Why did we choose to restrict the domain? We were trying +to make the point that invoking a procedure makes sense only with appropriate +arguments; that point is obscured by the complicating fact that Scheme +interprets any non-<CODE>#f</CODE> value as true. In the <CODE>functions</CODE> program, +where composition of functions is not allowed, there's no benefit to Scheme's +more permissive rule.<P> +<A NAME="ft6" HREF="functions-implement.html#text6">[6]</A> A reason that we +restricted the domains of some mathematical functions is to protect +ourselves from the fact that some version of Scheme support complex numbers +while others do not. We wanted to write one version of <CODE>functions</CODE> that +would work in either case; sometimes the easiest way to avoid possible +problems was to restrict some function's domain.<P> +<A NAME="ft7" HREF="functions-implement.html#text7">[7]</A> That last argument to <CODE>and</CODE> is complicated. The +reason we use <CODE>map</CODE> instead of <CODE>every</CODE> is that the results of the +invocations of <CODE>fn</CODE> might not be words or sentences, so <CODE>every</CODE> +wouldn't accept them. But <CODE>map</CODE> has its own limitation: It won't +accept a word as the <CODE>stuff</CODE> argument. So we use <CODE>every</CODE> to turn +<CODE>stuff</CODE> into a sentence—which, as you know, is really a list—and +that's guaranteed to be acceptable to <CODE>map</CODE>. (This is an example of a +situation in which respecting a data abstraction would be too horrible to +contemplate.)<P> +<P><A HREF="../ss-toc2.html">(back to Table of Contents)</A><P> +<A HREF="../ssch20/io.html"><STRONG>BACK</STRONG></A> +chapter thread <A HREF="../ssch22/files.html"><STRONG>NEXT</STRONG></A> + +<P> +<ADDRESS> +<A HREF="../index.html">Brian Harvey</A>, +<CODE>bh@cs.berkeley.edu</CODE> +</ADDRESS> +</BODY> +</HTML> |