Simply Scheme Chapter 21 – Example: The Functions Program

My work on this chapter, which looks at how thefunctions program from Chapter 2 works in the course of discussing input and output.


21.1 ❌

The get-args procedure has a let that creates the variable first, and then that variable is used only once inside the body of the let. Why doesn’t it just say the following?

Unsure. Procedure seemed to work fine when I tried it with this code. Couldn’t figure out the issue.

Let’s see whatΒ Meng Zhang says:

; answer:
; Creating a variable to store the value of get-arg in get-args is necessary because the order to evaluate the subexpressions in cons is unspecified.

; If get-arg is put into cons directly and the user’s scheme interpreter evaluates subexpresssions from right to left, (get-args (- n 1)) will be evaluated before (get-arg) in cons. As a result, the first argument the user inputs will be put at the end of the argument list and cause error of “Argument(s) not in domain.”.

Ah interesting. So the fact that there didn’t appear to be anything wrong with me when I was running Scheme was the result of a mere accident of evaluation order of the version of Scheme I am happening to run. But if the evaluation order was the other way around, then the procedure might cons together the arguments 0 and 1 in the opposite order for the / function and thus lead to a domain issue.

21.2 βœ…

The domain-checking function for equal? is

This seems silly; it’s a function of two arguments that ignores both arguments and always returns #t. Since we know ahead of time that the answer is #t, why won’t it work to have equal?‘s entry in the a-list be

Because the in-domain? subprocedure wants a procedure it can call on the arguments to check whether they’re in the domain, so the final thing in the a-list for a given entry needs to be a procedure. With the proposed entry above, in-domain tries to apply #t to the arguments and this predictably leads to an error…

equal? has a really broad domain. It can take values like words and lists, booleans, functions, and numbers. So I’m guessing it’s a-list entry is written like this because we’re not really doing a serious domain check here, but we still need some function to plug into the a-list entry given the overall design of the program.

21.3 βœ…βŒ

(Half-credit for only addressing part of the problem)

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 assoc in *the-functions*. That’s inefficient. Instead, rewrite the program so that get-fn returns a function’s entry from the a-list, instead of just its name.

This requires changing the last line of get-fn to use assoc

Then rename the variable fn-name to fn-entry in the functions-loop procedure,

Note that I had to get the car of fn-entry to get the name in order for the check for exit to work properly. I did not do this initially, and it was a source of some confusion later on because exit was not working correctly and I was unable to discern why!

and rewrite the selectors scheme-procedure, arg-count, and so on, so that they don’t invoke assoc.

We keep the cadr and similar invocations in these selectors but refer to the fn-entry value instead of directly to the association list.

I also renamed an argument to the in-domain? procedure. No substantive code changes were required, since in-domain? relies on type-predicate to select the appropriate part of the entry from the association list, and type-predicate was already updated. I just changed the argument name for the sake of clarity of what value was being passed to in-domain?

These changes seem consistent with the changes Andrew Buntine made.

I noticed that Meng Zhang made some other changes. I think these are necessary for procedures like every to work correctly in the functions program. For example, when trying to invoke every with first as the first argument with only the changes I made above, I get the following:

What seems to be happening is that the procedure is trying to get the arg-count for first. arg-count is expecting a list that it can get the caddr of but is instead given the word first. At least some of the subprocedures within the functions program that deal with every and keep need to be addressed in a special way, since every and keep themselves are special cases that take functions as arguments. Because of that attribute, the changes I made above, which work with most procedures, cause problems for every and keep. I’m only giving myself half credit on this since I didn’t spot this issue. Here’s what Meng did:

21.4 βœ…

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 *the-functions* also contains a specific out-of-domain message like “both arguments must be numbers,” then modify functions to look up and print this error message along with “argument(s) not in domain.”

Here’s a rewritten in-domain? which accomplishes this:

Note that show-line takes a sentence and returns it without parentheses.

Note that I had to use cdr cuz 4 a/d letters in a row is all the version of Scheme I’m using will recognize for cadr type invocations.

Note that we only want to show the out-of-domain value if the value is actually out of domain, but we also want to return the value domain-status in either case. That’s why there are two calls to domain-status – one for when we show the “side effect” out-of-domain, and one for when we don’t.

I actually decided to revise in-domain? after realizing that I wanted to use strings and not sentences to represent the out-of-domain messages. show-line didn’t work for the purpose of returning a string but show does work.

I was doubtful whether this exercise would be worth going through initially, but some of the domains were quite tricky to think about and figure out, and it was good to get a different perspective on some of the stuff that we went over in Chapter 2 but with more detail.

Here is my modified list. Note that double blackslashes \\ are used for escaping parentheses inside strings.

Note: I liked how Andrew Buntine handled thisΒ – he used an err-message helper procedure which helped keep the code cleaner.

21.5 βœ…βŒ

(Partial credit for inelegance)

Modify the program so that it prompts for the arguments this way:

but if there’s only one argument, the program shouldn’t say First:

So I came up with a really ugly and tremendously inelegant solution for this, which I settled on after trying to do more elegant stuff involving procedures that take variable numbers of arguments and getting stuck. Basically, within functions-loop, I look to see if the arg-count for fn-entry is greater than 1. If it is, I go down one branch of the procedure involving two new procedures, get-args-many and get-arg-many, that are written to deal with a list of ordinal names, one of which is displayed before Argument: as appropriate.. If arg-count for fn-entry is not greater than 1, then the same get-args and get-arg procedures that were in use before get used with no list of ordinals, and so Argument: appears as before.
Here is the modified procedure in action:

And here’s the code for the stuff I changed or added. Note that for some reason I did a list of ten ordinal numbers but that was quite unnecessary!

I thought Meng Zhang’s solution was way more elegant. His get-args procedure checks if the number of arguments to the procedure is equal to 1. If so, it calls a get-args-single-helper procedure. Otherwise, it calls a get-args-multi-helper procedure that takes an extra argument which keeps track of whether the procedure is on the first, second, or third argument. Both these arguments only handle displaying the appropriate argument text/number before recursively calling themselves, and use the existing get-arg code for other stuff, thus avoiding a bunch of unnecessary duplication that I had.

21.6 βœ…

The assoc procedure might return #f instead of an a-list record. How come it’s okay for arg-count to take the caddr of assoc‘s return value if (caddr #f) is an error?

I think what actually happens is that fn-name invokes get-fn before we ever get to invoking arg-count. get-fn asks the user for the function and uses a procedure called valid-fn-name? to check whether it’s a valid function. If it’s not a valid function, then the user is prompted for a function again. So by the time arg-count gets around to invoking caddr, we’ve already established that a valid function is being sought and thus that assoc won’t return #f.

(checked my answer against Meng Zhang’s)

21.7 βœ…

Why is the domain-checking predicate for the word? function

instead of the following procedure?

The domain of the function word?, as distinct from the domain of the function word, needs to encompass things that are not words for it to be a useful procedure. You don’t want it to be able to only apply to words – you want it to be able to apply to a boolean or a number or a list and say “no, that’s not a word”. So for that reason you want the domain to be very broad. The lambda that takes a single argument and always returns true is a very broad domain checking predicate.

(checked my answer against Meng Zhang’s)

21.8 βœ…

What is the value of the following Scheme expression?

I think most of the program is side effects. I wonder if the answer is the final thing that gets returned if you successfully exit the program Β “Thanks for using FUNCTIONS!”. That’s my best guess but I am not sure. The other alternative would be something like “the value depends on the user input”.

Meng Zhang agreed with my best guess.

21.9 βœ…

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 get-fn reduce the size of the problem?

I guess the base case here would be getting a correctly named function. The recursive calls occur when the user has left the line empty, or typed more than one thing, or typed an invalid function. After verifying that one of those conditions has occurred and printing an error message, the program tries a recursive call in order to give the user another opportunity to enter valid input. This isn’t the same thing as just cdring through a list, because the user may or may not enter valid input, but it’s at least potentially involves getting closer to what you could call a base case here.

Meng Zhang agreed.

Leave a Reply

Your email address will not be published.