Simply Scheme Chapter 4 – Defining Your Own Procedures

How to Define a Procedure

Essential aspects of defining a procedure, with an example:

  1. The word define, which indicates that you are defining something.
  2. The name you want to give the procedure.
  3. The name(s) you want to give its argument(s).
  4. Body: what the procedure actually does. “an expression whose value provides the function’s return value.”

Special Forms

define is an example of a special form. Special forms have their own evaluation rules. For define in our square example, none of the parts got evaluated, which is different than how Scheme normally treats expressions. Normally, Scheme evaluates sub-expressions and then evaluates the whole expression once the sub-expressions have been evaluated. But that wouldn’t make sense to do with define.

define isn’t actually a procedure at all in a technical sense. But as a simplification, the book discusses define as if it is a procedure, unless there is some important reason not to.

Functions and Procedures

Function — an association between the starting value(s) and the resulting value, no matter how that result is computed. The same function can be represented by different operations.

The book notes that there is a distinction between a function and a procedure, and that functions can be represented in different ways (such as a table that represents the relationship between US States and state capitals; I also came up with the example of coordinates that indicate a certain location on a map – the coordinates are the starting value, the specific location on a map is a return value, and the relationship between coordinates and locations is represented by the map, and might even be represented three-dimensionally by a globe).

Argument Names versus Argument Values

Formal parameter – what you call an argument as defined in a function. E.g. for…

the formal parameter is x. It’s a placeholder value for an actual argument.

Actual argument expression – an expression that serves as the actual argument for a procedure.
Actual argument value – the specific value that a procedure receives in a case when it is invoked.
Example: for (square (+ 5 9)), (+ 5 9) is the actual argument expression, and the actual argument value is 14.

Substitution Model

Today’s story is about the substitution model. When a procedure is invoked, the goal is to carry out the computation described in its body. The problem is that the body is written in terms of the formal parameters, while the computation has to use the actual argument values. So what Scheme needs is a way to associate actual argument values with formal parameters. It does this by making a new copy of the body of the procedure, in which it substitutes the argument values for every appearance of the formal parameters, and then evaluating the resulting expression. So, if you’ve defined square with

then the body of square is (* x x). When you want to know the square of a particular number, as in (square 5), Scheme substitutes the 5 for x everywhere in the body of square and evaluates the expression. In other words, Scheme takes

then does the substitution, getting

and then evaluates that expression, getting 25.

Shorter Points

Abstracting out a pattern and giving it a name is important. It lets you deal with certain problems without having to worry about the details.

Composition of functions is the basis for all Scheme programming.

Pitfalls

Functions can only have one return value. If you return multiple values, Scheme will ignore earlier ones and return the last one.

Don’t use the name of a procedure as a formal parameter. If you use the same name with two meanings within the same procedure (as e.g. parameter and procedure) it will cause a problem.

Similar issue as above: don’t use the name of a keyword (such as define) as some other kind of name.

Don’t try to write a procedure that has a complex expression as a formal parameter.

Questions

  1. In what important cases, if any, does define not being a “real” procedure make a difference?

Exercises

✅ Exercise 4.1

Show the substitution that occurs when you evaluate

✅🤔Exercise 4.2

Given the following procedure:

list all the little people that are involved in evaluating

Substituting the parameter in:

(Give their names, their specialties, their arguments, who hires them, and what they do with their answers.)

There are three little people. I’ll give them names (like in a prior exercise) and say what they do.

  • Adam is a yawn specialist and hires everyone else. Yawn takes the (/ 8 2) expression and substitutes it into the body of yawn. He has nobody that he reports to.
  • Bob is an addition specialist. His arguments are 3 (given directly) and 8 (given by Charlie). The result is 11, and this is reported to Adam.
  • Charlie is a multiplication specialist. His arguments are 4 (given by Donald) and 2 (given directly). The result is 8. This result is reported to Bob.
  • Donald is a division specialist. His arguments are 8 and 2 (both given directly). The result is 4, and this resulted is reported to Charlie.

Note: AnneB talks about a “head boss” who hires the yawn specialist. This tracks how the book talked about this. I’m unclear whether this is an important detail or whether the book was just trying to be colorful with the hiring metaphor.

✅ Exercise 4.3

Here are some procedure definitions. For each one, describe the function in English, show a sample invocation, and show the result of that invocation.

Note: I edited some of these for a mistaken use of “parameter” in place of “argument”.

✅ 4.3.1

This procedure finds the difference between the two arguments provided; specifically, it takes the second argument and subtracts the first argument from it.

A sample invocation:

✅ 4.3.2

This procedure returns as a result whatever is provided as the argument. Sample invocations:

✅ 4.3.3

This procedure takes an argument but just returns 3, regardless of the argument provided.

✅ 4.3.4

This procedure does not take an argument. It merely defines the name seven as 7.

✅ 4.3.5

This procedure takes a number argument and performs a series of arithmetical operations on the number which result in the same number being returned.

the number n is multiplied by 3, added to 13, added to the difference of itself and 1, divided by 4, and then has 3 subtracted.

So for example, 10 is multiplied by 3, producing 30.
It’s then added to 13, producing 43.
It’s added to (10 – 1), so now it’s 52.
It’s divided by 4, so it’s now 13.
Then 3 is subtracted, so it’s back to 10.

Sample invocation:

Note: AnneB actually did the math to make sure this function always returns the given argument. 👍

✅ Exercise 4.4

Each of the following procedure definitions has an error of some kind. Say what’s wrong and why, and fix it:

✅ 4.4.1

As written, this expression is trying to return multiple values, and so the earlier value is getting ignored. What the programmer wants to do is multiply these values together. This can be accomplished by deleting the ) to the right of the 3.141592654. Fixed:

✅ 4.4.2

This one is using infix notation but Scheme uses prefix notation.

✅ 4.4.3

This one forgot to give square a formal parameter.

✅ 4.4.4

This doesn’t designate “base” or “height” as the formal parameters, but uses them in the body anyways. Thus these are just undefined terms and Scheme doesn’t know what to do with them. One possible fix:

✅ 4.4.5

This is using complex expressions for the formal parameters. This is trying to do some of the job of the procedure invocation stage at the definition stage.

instead do this:

and if you want to square the arguments, just do something like:

✅ Exercise 4.5

Write a procedure to convert a temperature from Fahrenheit to Celsius, and another to convert in the other direction. The two formulas are F=9⁄5C+32 and C=5⁄9(F-32).

✅ Exercise 4.6

Define a procedure fourth that computes the fourth power of its argument. Do this two ways, first using the multiplication function, and then using square and not (directly) using multiplication.

Way 1:

Note: AnneB’s solution is better, as she makes use of the fact that * takes multiple arguments, so the result is more elegant.

Way 2:

✅ Exercise 4.7

Write a procedure that computes the absolute value of its argument by finding the square root of the square of the argument.

✅ Exercise 4.8

Note that I changed the formatting of the exponents cuz I don’t think Ulysses does superscript.

“Scientific notation” is a way to represent very small or very large numbers by combining a medium-sized number with a power of 10. For example, 5 x [10^7] represents the number 50000000, while 3.26×[10^-9] represents 0.00000000326 in scientific notation. Write a procedure scientific that takes two arguments, a number and an exponent of 10, and returns the corresponding value:

Some versions of Scheme represent fractions in a/b form, and some use scientific notation, so you might see 21/50000 or 4.2E-4 as the result of the last example instead of 0.00042, but these are the same value.

Solution for scientific:

❌ Harder Probem for Hotshots

(A harder problem for hotshots: Can you write procedures that go in the other direction? So you’d have

You might find the primitive procedures log and floor helpful.)

I could not figure out how to use log and floor effectively.

After trying for a while and writing the stuff about getting unstuck at the end of the post, I started reading Anne’s answer. I’m taking the approach I talk about later in the post of seeing “if it’s possible to do ‘partial spoilers’, like get a hint from someone or only read the beginning of an answer, so you can get unstuck while still figuring some things out.”

Anne says:

I wrote these as a first try:

(define (sci-exponent n)
(floor (log n)))

(define (sci-coefficient n)
(/ n (expt 10 (sci-exponent n))))

But I saw that in my program, log returns the natural log, not the base 10 log.

Yeah okay, I did get as far as noticing that.

I looked in the DrRacket documentation and saw that if you give a second argument to log then it’s supposed to use the second argument as a base.

Ah! I’m gonna add “check the documentation” to a list of things at the end of the post.

More AnneB:

That did not work in my version—it said it expected one argument and not two.

I searched some more and found this, which shows how to get a base 10 log:

Here’s the usual way to do that.

(define (log10 n) (/ (log n) (log 10)))
(log10 100)
2.0

Ah so that’s interesting. I came across something like this as well. I think I didn’t read it cuz I wanted to try to figure out the problem using the stuff I’d already learned from the book. That’s a bit silly though. After you’ve been stuck for a while, it’s worth trying out different things. It’s possible that if I’d checked the documentation like Anne did and had seen how log was supposed to work, I would have been more open to trying different things.

With the very big hint re: the log10 stuff and Anne’s other discussion so far, I was able to write procedures successfully:

Let me try to explain what each part is doing. Warning – I try to explain math stuff a bit so this might be a little muddled.

log10 is dividing the natural logarithm of the argument number by the natural logarithm of 10. In math, there is something called the change of base formula.

Suppose you want to evaluate the logarithm of a number in some base that isn’t on a calculator or that you don’t have a built-in programming language function for figuring out. The change of base formula lets you evaluate the logarithm of some number in whatever base you want. The first step is to find the logarithm of the number in whatever base you have access to a logarithm function for. Recall that log in Scheme finds the natural logarithm. Then we divide by the logarithm of the base we actually want to use(again, in whatever base we have access to, which for Scheme is the natural logarithm).

I found an example:


So for our case, mathematically, we want to find the natural logarithm of some number and then divide that by the natural logarithm of 10. That is what the log10 function accomplishes.

If we get call log10 on 7300, we get 3.8633228601204554. That is the power we would have to raise 10 to in order to get 7300. That makes sense, since raising 10 to 3 gets us 1000 and raising 10 to 4 gets us 10,000, so the value for 7300 has to be within 3 and 4.

3.8633228601204554 isn’t a great number for the purposes of sci-exponent though. The purpose of sci-exponent is to tell us what power of 10 we’d have to raise 10 to so that (10 raised to some power) multiplied by some coefficient will produce some number. We know that 10^3 is 1000 and 10^4 is 10,000. 3.8633228601204554 is a long number with a lot of stuff after the decimal cuz it needs to raise 10 to 7300. One way to think about things is that the 3 gets the 10 to 1000 and the .8633228601204554  gets us the rest of the way to 7300. But for the purposes of sci-exponent we’re just interested in the 3 (or whatever whole number exponent) and not interested in the stuff after the decimal. Think of the number 7300. One way of representing it is 7.3 x 1000. The coefficient part of scientific notation is the part on the left – the 7.3. The purpose of sci-exponent is to figure out the value on the right – and specifically to figure it out in exponential form (as in, 10 to what power produces the value on the right).

We know that scientific notation involves a coefficient and an exponent combining to concisely express a large or small number. If we took the log10 of some number and rounded up, and then tried to multiply the result from doing that with the coefficient, we’d get the incorrect answer. For example, if we rounded 3.8633228601204554 up to 4, and then multiplied that by 7.3, we’d get 73000. So for the purposes of figuring out sci-exponent, we want to always be rounding the log10 down. That is what floor accomplishes – it rounds down to the next integer.

sci-coefficient simply divides the number by 10 raised to the sci-exponent of the number. This produces the coefficient, as you would expect.

Some test cases and results:

✅ Exercise 4.9

Define a procedure discount that takes two arguments: an item’s initial price and a percentage discount. It should return the new price:

(discount 10 5)
9.50

(discount 29.90 50)
14.95

Note: AnneB‘s version is more elegant because she just multiplied original-price, 0.01 and what i call percentage-reduction together, rather than doing more nesting and dividing by 100.

✅ Exercise 4.10

Write a procedure to compute the tip you should leave at a restaurant. It should take the total bill as its argument and return the amount of the tip. It should tip by 15%, but it should know to round up so that the total amount of money you leave (tip plus original bill) is a whole number of dollars. (Use the ceiling procedure to round up.)

(tip 19.98)
3.02

(tip 29.23)
4.77

(tip 7.54)
1.46

One caveat: the (tip 19.98) test case produces some kind of rounding error that can be replicated doing other simple arithmetic operations like (- 23 20.98). AnneB had this issue as well.

Troubleshooting an Instance of Getting Stuck

I got stuck on the “harder problem for hotshots” from Exercise 4.8. I’m going to write some thoughts about that. I broke the thoughts up into two categories: 1) stuff that I think is more about how to approach getting stuck in general and 2) stuff that seems more specific to getting unstuck while doing programming.

Getting Unstuck in General

  • Consider the time-based metric for overreaching. I went way over 15 minutes being stuck this time.
    • when thinking of time-based metric for overreaching, consider setting an actual timer once you realize you are getting stuck. Give yourself e.g. 15 minutes to get unstuck, and if the timer goes off, just move on with other parts of the project/life. It’s very easy for me (and I bet for other people) to get caught in biased “I just need five more minutes to figure this out” loops, so external checks/alerts like a timer can help with that. (This is a bit like the gambler who thinks things will turn around on the next roll of the dice. The fact that sometimes things do turn around doesn’t make this approach any less of a bad strategy – you gotta think about things in terms of expected return instead of focusing on outlier outcomes).
  • Consider alternate uses of time within the project – e.g. consider whether it is worth spending an hour being stuck when you could just read the answer and move on and actually learn more stuff.
  • Consider alternate “meta” uses of time – e.g. it might be way more valuable to spend an hour writing learning methodology troubleshooting analysis like this rather than spend it on being stuck.
  • Think about motivation for spending time on thing you’re getting stuck on – e.g. do I wanna solve the problem for “hotshots” cuz I want to be a “hotshot”? Second-handedness issue there maybe…
  • Think about immediate importance of resolving issue – e.g. if it’s a problem for “hotshots” then that’s an indicator there might not be a strong expectation you can solve the problem right now, so you’re not “behind” or failing or anything, so you don’t need to worry about that.
  • If it’s something with an “answer”, see if it’s possible to do “partial spoilers”, like get a hint from someone or only read the beginning of an answer, so you can get unstuck while still figuring some things out yourself.
  • Review past thoughts/notes regarding learning methodology.
  • Try to have a big picture perspective regarding how the project is going rather than focusing in on one part that you got stuck on and making that a major issue in your mind.
  • Ask for help!

Programming Project Troubleshooting

  • Specify in detail all your project requirements – e.g. what test cases are you expecting the program to have to solve?
  • Specify in detail all assumptions, restrictions, limitations that seem important and relevant to what you are stuck on — e.g. I was trying to limit how I approached the problem I got stuck on by only using functions that had already been meaningfully introduced – so no recursion or cond. That’s a big limitation that’s worth explicitly stating, at least.
  • If any of the information or advice from the problem seems unusable or irrelevant, at least say so and say what you did to try to figure out whether it was relevant or not — e.g. I couldn’t figure out how to use floor or log in the problem I got stuck on, but didn’t really go into detail about what I had tried or thought of in terms of how I might use them.
  • Check the documentation – AnneB discovered that log is supposed to work in a certain way by checking the docs.
  • Get super organized –
    • make sure your attempted solutions are nicely formatted.
      • do detailed commenting on what each part does.
      • keep track of each problem solving attempt rather than deleting it. This will let people help you more effectively by showing them how you’re thinking about the problem.

Review of Past Learning Attempts

This is the first chapter I have some substantial work from the past to review.

Something worthy of note is that I basically did not get anywhere with sci-coefficient and sci-exponent the last time around, though I did figure out scientific. I seemed to have solve the other problems in a very similar manner.

Learning Methodology

I set a goal for this chapter to try to ask more explicit questions but wound up getting very side-tracked with the getting stuck thing. However, I thought I wrote some good thoughts about that topic, so that seems okay.

Simply Scheme Chapter 3 – Expressions

Quote from here:

The big idea in this part of the book is deceptively simple. It’s that we can take the value returned by one function and use it as an argument to another function. By “hooking up” two functions in this way, we invent a new, third function. […]
We know that this idea probably doesn’t look like much of a big deal to you. It seems obvious. Nevertheless, it will turn out that we can express a wide variety of computational algorithms by linking functions together in this way. This linking is what we mean by “functional programming.”

Chapter 3 – Expressions

Quotes from here.

Read-eval-print loop – Interaction between you and Scheme.

Scheme reads what you type, evaluates it, and prints the answer, and then does the same thing over again.

Expressions – a question you type into Scheme.

Atomic expression – a single value, such as 26.
Compound expression – an expression made out of smaller expressions, such as (+ 14 7).

Scheme versus other languages:

If you’ve programmed before in some other language, you’re probably accustomed to the idea of several different types of statements for different purposes. For example, a “print statement” may look very different from an “assignment statement.” In Scheme, everything is done by calling procedures, just as we’ve been doing here. Whatever you want to do, there’s only one notation: the compound expression.

Little People

This chapter uses a model of little people to explain how an expression like (- (+ 5 8) (+ 2 4)) gets evaluated.

A tree depicting some stuff discussed in the chapter:

The parentheses are key for guiding the workflow:

How does Alonzo know what’s the argument to what? That’s what the grouping of subexpressions with parentheses is about. Since the plus expressions are inside the minus expression, the plus people have to give their results to the minus person.

The book has an important clarification about the descriptive model it uses:

We’ve made it seem as if Bernie does his work before Cordelia does hers. In fact, the order of evaluation of the argument subexpressions is not specified in Scheme; different implementations may do it in different orders. In particular, Cordelia might do her work before Bernie, or they might even do their work at the same time, if we’re using a parallel processing computer. However, it is important that both Bernie and Cordelia finish their work before Alice can do hers.

Bernie and Cordelia must finish their work before Alice can do hers because Alice can’t know what she’s supposed to subtract from what until Bernie and Cordelia are finished. So the very nature of Alice’s workflow is dependent upon what Bernie and Cordelia discover. However, Bernie and Cordelia’s workflows are independent from each other, so they can happen in whatever order or at the same time.

We can express this organization of little people more formally. If an expression is atomic, Scheme just knows the value.[3] Otherwise, it is a compound expression, so Scheme first evaluates all the subexpressions (in some unspecified order) and then applies the value of the first one, which had better be a procedure, to the values of the rest of them. Those other subexpressions are the arguments.

Makes sense.

(Scheme ignores everything to the right of a semicolon, so semicolons can be used to indicate comments, as above.)

Ah I’d forgotten what the indicator for comments was 🙂

Results Replacement

To keep track of which result goes into which larger computation, you can write down a complicated expression and then rewrite it repeatedly, each time replacing some small expression with a simpler expression that has the same value.

In each line of the diagram, the boxed expression is the one that will be replaced with its value on the following line.

They’re going very step-by-step here to show how the program works and avoid confusion, which is good.

If you like, you can save some steps by evaluating several small expressions from one line to the next:

I think this is how I would do it, since only replacing one expression at a time seems pretty tedious.

Plumbing Diagrams

This section has some pictures depicting a procedure as a machine. I often think in terms of machine metaphors for procedures, especially when they do things like add or remove something to some input data. I find it easy to analogize certain programs to the sort of thing that might happen on an assembly line.

Here is Simply Scheme’s diagram for (- (+ 5 8) (+ 2 4)):

There is some similarity to the rightmost tree of the two trees I made above (though I used words).

Pitfalls

The book lists some Scheme pitfalls.

The first pitfall is people reading programs from left to right rather than thinking in terms of expressions and subexpressions. I don’t think I have that particular issue, though I can see it being a big problem. I think I found it fairly intuitive to imagine the subexpressions flowing up through the structure of the procedure as they are evaluated. I think it is particularly important to be able to think of things that way when you try to think about recursion, cuz you’ve got to think of what happens when the base case gets reached and then what happens to the result that is returned from the base case and so on.

Another pitfall is that people think Scheme cares about white space. They emphasize that they’ve been indenting things for readability but that the parentheses are what actually matters. Contrast this with Python, which does care about white space.

Scheme treats these as the same
Scheme treats these as the same

A consequence of Scheme’s not caring about white space is that when you hit the return key, Scheme might not do anything. If you’re in the middle of an expression, Scheme waits until you’re done typing the entire thing before it evaluates what you’ve typed.

So if you forget a paren and hit return, nothing will happen. You’ve gotta finish your expression before Scheme will try to evaluate it.
I hit enter after the following expression and you can see what Racket did – it just went to the next line but didn’t try to evaluate the expression:

The book also mentions that stray quotation marks " can cause trouble, if you just have one and it isn’t paired off.

They also say:

One other way that Scheme might seem to be ignoring you comes from the fact that you don’t get a new Scheme prompt until you type in an expression and it’s evaluated. So if you just hit the return or enter key without typing anything, most versions of Scheme won’t print a new prompt.

Yeah, for me Racket just keeps going down a line but with no new > if I keep hitting enter, at least until I actually do something:

Exercises

Some files for this chapter are posted in my Github.

✅ Exercise 3.1

Translate the arithmetic expressions (3+4)×5 and 3+(4×5) into Scheme expressions, and into plumbing diagrams.

my attempt at something like a plumbing diagram for the above expression:

✅ Exercise 3.2

How many little people does Alonzo hire in evaluating each of the following expressions:

I think the way to think of this is “how many operations were performed?” So you just count up the number of operations like +, *, and -. This also matches the number of open or closing parentheses, respectively.

I think there are 3 little people in this example. In this case, the + takes 3 values, but I don’t think that affects the number of little people.

4 little people for this one.

I counted 10 operations. I then cross-checked that against the number of opening parentheses and closing parentheses. One advantage of going by the number of parentheses is you can use a text editor to count those with the Find feature 🙂

NOTE: I’m generally checking my answers against AnneB’s to ensure accuracy and watch for mistakes. AnneB came out the same on this problem and said:

I think it’s the same as the number of procedures, which is the same as the number of left parentheses.

Yep 🙂 I’d only add that it’s also the number of right parentheses, since the number of parentheses have to match.

✅❌ Exercise 3.3

Each of the expressions in the previous exercise is compound. How many subexpressions (not including subexpressions of subexpressions) does each one have?

For example,
(* (- 1 (+ 3 4)) 8)
has three subexpressions; you wouldn’t count (+ 3 4).

Earlier in the chapter the book said:

(* (- (+ 5 8) (+ 2 4))
(/ 10 2))
35

This says to multiply the numbers 7 and 5, except that instead of saying 7 and 5 explicitly, we wrote expressions whose values are 7 and 5. (By the way, we would say that the above expression has three subexpressions, the * and the two arguments. The argument subexpressions, in turn, have their own subexpressions. However, these sub-subexpressions, such as (+ 5 8), don’t count as subexpressions of the whole thing.)

So it seems like we count sub-expressions by counting up the procedure and the number of arguments being passed to the procedure at the highest/final “level” of the expression.

(❌ERRONEOUS STATEMENT) So this:

(+ 3 (* 4 5) (- 10 4))

has 3 sub-expressions. (the +, an expression whose value is 20, and an expression whose value is 6.

✅EDIT/CORRECTION: I somehow forgot to count the 3! Whoops.I think maybe since I’d just written “has 3 sub-expressions” I somehow thought I’d already addressed the 3? Not sure. Thanks to AnneB for helping me catch this error by having the correct answer on her blog. Also, I think AnneB’s way of noting the sub-expressions by writing out each one is better for avoiding mistakes.

This one:

(+ (* (- (/ 8 2) 1) 5) 2)

has 3 sub-expressions: +, an expression whose value is 15, and an expression whose value is 2.

For the next one, I wanted to simplify the sub-expressions in a somewhat step-by-step way in order to check my answer. For the sub-expression (/ 2 3), when simplifying, I used the decimal notation 0.6666666666666666.

simplifies somewhat to this:

which further simplifies to this:

and finally to:

So there are three sub-expressions.

Another way to figure out the number of sub-expressions is to look at the highlighting.

If we put the cursor on the outside of the first open parenthesis, then the whole expression highlights. The * operates at the highest “level” of the procedure, so this highlighting corresponds to the level the * operates on.

With the cursor to the left of the open parenthesis that precedes +, we see another grouping. The highlighting indicates that everything inside is one big group that gets evaluated and ultimately handed to the * at the top level. So the whole highlighted thing counts as one sub-expression.

Same idea here. The whole highlighted part ultimately only counts as one sub-expression for our *.

So there are three sub-expressions:

*

and

I think that there are other ways you could figure out the number of sub-expressions. You could use trees, for example. I feel like I’m okay on this point though.

✅ Exercise 3.4

Five little people are hired in evaluating the following expression:

Give each little person a name and list her specialty, the argument values she receives, her return value, and the name of the little person to whom she tells her result.

  • Adam: Addition specialist. Receives arguments -9 (from Bob) and 10 (From Donald). Returns 1. Highest level little person, no direct report.
  • Bob: Multiplication specialist. Receives arguments 3 (directly) and -3 (from Charlie). Returns -9. Reports result to Adam.
  • Charlie: Minus specialist. Receives arguments 4 and 7 directly. Returns -3. Reports result to Bob.
  • Donald: Minus specialist. Receives argument values 8 (directly) and -2 (from Evan). Returns 10. Reports result to Adam.
  • Evan:Minus specialist. Receives argument values 3 and 5 directly. Returns -2. Reports result to Donald.

I made a diagram.

✅ Exercise 3.5

Evaluate each of the following expressions using the result replacement technique:

thing to be replaced in next line is in bold

(sqrt (+ 6 (* 5 2)))
(sqrt (+ 6 10))
(sqrt 16)
4

(+ (+ (+ 1 2) 3) 4)
(+ (+ 3 3) 4)
(+ 6 4)
10

✅ Exercise 3.6

3.6 Draw a plumbing diagram for each of the following expressions:

I remembered that I have an iPad. Let’s try doing some plumbing diagrams with that

A little rough but it does the job!

✅ Exercise 3.7

What value is returned by (/ 1 3) in your version of Scheme? (Some Schemes return a decimal fraction like 0.33333, while others have exact fractional values like 1/3 built in.)

A fraction:

✅❌ Exercise 3.8

Which of the functions that you explored in Chapter 2 will accept variable numbers of arguments?

I checked this list from that chapter:

+, -, /, \<=, \<, =, >=, >, and, appearances, butfirst, butlast, cos, count, equal?, every, even?, expt, first, if, item, keep, last, max, member?, not, number?, number-of-arguments, odd?, or, quotient, random, remainder, round, sentence, sqrt, vowel?, and word.

I got the following list of functions:

❌INCOMPLETE:

✅CORRECTION: AnneB noticed that random will take two arguments (and not just one) if the first argument you give it is less than the second. Strange behavior!

Also, Anne’s list is missing - and \. The behavior with these seems to be to apply the procedures to the first two arguments and then to the result of that plus the next argument, down the line until all the arguments have been dealt with.

✅ Exercise 3.9

The expression (+ 8 2) has the value 10. It is a compound expression made up of three atoms. For this problem, write five other Scheme expressions whose values are also the number ten:

• An atom

10

• Another compound expression made up of three atoms

(+ 3 7)

• A compound expression made up of four atoms

(+ 3 3 4)

• A compound expression made up of an atom and two compound subexpressions

• Any other kind of expression

let’s do a compound expression made up of an atom and three compound subexpressions:

Simply Scheme Chapter 2 – Functions

Getting Started

This chapter has us start off by loading a “functions.scm” file which can be downloaded here. You want to load the file by typing (load "functions.scm") in the Interactions window of DrRacket, and not in the Definitions window. If you try loading it in Definitions window, you may get an error message like I did.

Once the file is loaded, the program does not automatically start; you need to type (functions) to start the program. The functions program creates a special way of entering functions that is different than the normal way that scheme operates. It gives you a prompt to enter a function and then prompts you to enter arguments. The number of arguments you can enter varies. For example, sqrt only lets you enter one argument, which makes sense. OTOH, + and se let you enter two arguments. It’s worth noting that the number of arguments does not necessarily correspond to what the “real” Scheme function lets you enter – for instance, you can enter (+ 1 2 3 4 5 6) in Scheme no problem and get an answer, but the functions program does not let you do that. You can exit the functions.scm program by typing exit instead of typing the name of a function.

Playing With Functions

Math Functions

I tried performing some of the operations suggested by the chapter:

If I enter 1 ÷ 987654321987654321 in a calculator program, I get this

So I was expecting something similar to that for the / function.

The math functions don’t like words:

Some playing around with the odd? function, which tests whether a number is odd. Note the message related to not being in the domain for the argument 3.5.

remainder

Remainder:


A book-suggested example:

another example:

And another:

AnneB notes:

The sign of the result is the same as the sign of the first argument, which is the dividend. So I think remainder gives the remainder that results when both arguments are non-negative, and then makes the remainder negative if the first argument is negative.

Yeah this seems to be the case.

The remainder function doesn’t seem to like decimals:

expt

The book says:

Some two-argument functions have complicated domains because the acceptable values for one argument depend on the specific value used for the other one. (The function expt is an example; make sure you’ve tried both positive and negative numbers, and fractional as well as whole-number powers.)

Some experiments with expt

So you can see that with -2 and -1/2 as arguments I managed to get something that was outside the domain.

AnneB says:

I think the domain rule for expt is:

  • the first argument can be any real number
  • if the first argument is positive, the second argument can be any real number
  • if the first argument is 0, the second argument can be a non-negative real number
  • if the first argument is negative, then the second argument can be 0 or it can be a real number with absolute value greater than or equal to 1

Let’s try some more, with an eye towards seeing if AnneB is right!

That’s a negative number for the first argument, and a real number with an absolute value greater than 1 for the second argument, but this set of arguments appears to not be in the domain. So that appears to contradict AnneB’s final claim re: domain (though I could be wrong).

If the first argument is negative, it doesn’t seem like the expt function treats fraction or decimal values for the second argument as being within the domain. You can enter negative integer numbers for the second value, though (along with 0 and positive integers). So it is possible that Anne’s final claim should be redrafted as: “if the first argument is negative, then the second argument can be any integer.”

Rounding Mystery 🔍🧐

Another book example:

This is the result of my playing with the round function a bit:

Very interesting. So at some point in between 17.4999 and 17.499999999999999999999 something happens. My wild guess is that DrRacket/Scheme only keeps track of numbers to a certain level of precision regardless of how many digits you enter, and then rounds the number off internally (to whatever level of precision it keeps track of the numbers), and that if use a number that happens to be long enough to run into this issue and then try to feed that number into a function that rounds the number off, you basically get two levels of rounding, leading to the result of 17.499999999999999999999 getting rounded off to 18.

Hey I thought of a way to test this. Since Scheme returns the number you give it back to you, if what I’m saying is actually true, then normal Scheme should give me back a rounded-off number once I hit a certain number of digits.

Let’s see 🖥🧐

Yep. It looks like it’ll do 14 digits of precision after the decimal point, but once it hits that 15th digit, Scheme is like “nah” and rounds the number off to 17.5. And the final line there is just testing to make sure the threshold is digits after the decimal point (which it appears to be).

I vaguely remember running into an issue like this before in a previous run through Scheme or SICP or something. Maybe I’ll come across it as a I review my old work.

Word Functions

Some more book suggestions:

butfirst returns all but the first letter of a word. So if you give it a one-letter word, you get nothing. But if you give it a multi-letter word:

butfirst treats numbers in the same way it treats words:

and first behaves similarly

butfirst won’t count arguments with multiple words in them:




Apparently the following version is supposed to work:

But I and other people are having trouble with the functions.scm functions not accepting sentences in our versions of Scheme.

word takes 2 arguments and doesn’t respect spaces:


And you can’t use quotes (this seems to be true in general for the functions in functions.scm)
Whereas in the normal Scheme version of word, you can use spaces by putting a word in quotes and having a space at the end or by quoting a space separately:

both functions.scm word and normal Scheme word treat numbers as strings of characters:


Count

Now onto count:

count is counting up the number of digits in the argument you give it, and it does that for sequences of numbers as well as for words.

The regular Scheme version of count handles this numerical sequence the same way:

count doesn’t like spaces in words:

count could conceivably just treat the whole thing as a third-character sequence, one of which is a space, but it doesn’t.

The normal Scheme version of count handles this situation differently than the functions.scm count. It thinks the “United” is trying to refer to some definition for “United” that it can’t find:

Normal scheme count has the same behavior just for “United”

whereas the functions.scm count can handle “United” by itself and produce the result that you would expect:

Normal Scheme count can, of course, deal with “United” if you indicate that it is a word/string by putting a mark before the “United”:

Whereas the functions version of count actually treats a as part of the sequence of characters that it attempts to count up:

Ifs and Booleans

if takes 3 arguments:


If the first argument returns true, then the second argument gets returned. If the first argument returns false, then the third argument gets returned.

Functions

The book provides some examples of functions that take other functions:

The range of number-of-arguments is nonnegative integers. But its domain is functions. For example, try using it as an argument to itself!

Heh okay.

As expected 🙂

If you’ve used other computer programming languages, it may seem strange to use a function—that is, a part of a computer program—as data. Most languages make a sharp distinction between program and data. We’ll soon see that the ability to treat functions as data helps make Scheme programming very powerful and convenient.

Ah okay, so this is a notable feature of Scheme in particular.

The book suggests trying this example:

So the keep function is taking the vowel? function and a word as arguments and keeping the vowels.

Another example of this kind of thing:

Some functions that take other functions aren’t defined in the functions program:

Domain and Range

Simply Scheme Chapter 2 says:

So far most of our functions fall into one of two categories: the arithmetic functions, which require numbers as arguments and return a number as the result; and the word functions, which accept words as arguments and return a word as the result. The one exception we’ve seen is count. What kind of argument does count accept? What kind of value does it return? The technical term for “a kind of data” is a type.

count takes strings of characters (could be letters, numbers, both) which we might call a “word” and returns a number.

The technical term for “the things that a function accepts as an argument” is the domain of the function. The name for “the things that a function returns” is its range. So the domain of count is words, and the range of count is numbers (in fact, nonnegative integers). This example shows that the range may not be exactly one of our standard data types; there is no “nonnegative integer” type in Scheme.

Right okay. A function’s range doesn’t have to be something Scheme knows about.

How do you talk about the domain and range of a function? You could say, for example, “The cos function has numbers as its domain and numbers between −1 and 1 as its range.” Or, informally, you may also say “Cos takes a number as its argument and returns a number between −1 and 1.”[3]

For functions of two or more arguments, the language is a little less straightforward. The informal version still works: “Remainder takes two integers as arguments and returns an integer.” But you can’t say “The domain of remainder is two integers,” because the domain of a function is the set of all possible arguments, not just a statement about the characteristics of legal arguments.[4]

Yeah okay that makes sense. For example, the regular Scheme version of + can take no arguments or a ton of arguments. You can’t say “the domain of + is two arguments” just because 2 is a permissible number of arguments. The domain is supposed to be a generally true statement about the things that a function accepts as an argument.

I am getting “Argument(s) not in domain.” errors when I’m not supposed to when a sentence is an argument. I haven’t figured out how to resolve this as of yet. Fortunately, the issue appears to only affect the functions.scm functions and that file is only used for Chapter 2, so I don’t think this is a huge deal.

Exercises

Use the functions program for all these exercises.

For some of these, I ran into the problem with sentences not being in the domain of functions.scm functions, so I tested them out in regular Scheme. I had to define vowel? in regular Scheme – for that, I just stole code from Chapter 8.

For the purpose of answering these questions, i’m going to assume that if a function takes a number as an argument but treats it like a word, that the type of the number-as-word counts as “word” and not “number.”

Also, the data types I’m focusing on in my answers are words, numbers, booleans, and functions. I don’t care so much about data subtypes like “positive integers” though I may mention them. Sentence-handling is a bit broken in the functions used in this chapter, as mentioned above.

Exercise 2.1

In each line of the following table we’ve left out one piece of information. Fill in the missing details.

I just made up a word for the eieio line 🙂

Exercise 2.2

2.2 What is the domain of the vowel? function?

vowel? accepts words, sentences, booleans, functions, and numbers as arguments (in the sense that it won’t reject these as outside its domain). Its range is a boolean value – it returns true if you give it a single letter vowel, and false if you give it anything else.

AnneBfound a couple of examples of things outside vowel‘s domain.

Exercise 2.3

2.3 One of the functions you can use is called appearances. Experiment with it, and then describe fully its domain and range, and what it does. (Make sure to try lots of cases. Hint: Think about its name.)

Note: Correction below

appearances takes two arguments and appears to return the number of times that the first argument appears in the second argument if the first argument is a single digit letter, number, or punctuation mark.

If the first argument is a multi-character sequence, appearance returns zero, regardless of whether or not the sequence provided as the first argument appears in the second argument.

appearances says that sentences are outside its domain but that may be related to the technical issue described earlier. appearances will accept sentences as arguments in normal Scheme but does not appear to actually function as you might guess it would on sentences, as it will return 0 (instead of 1) even if passed identical sentences for both arguments.

The domain appears to be sequences of alphanumeric characters and punctuation marks (which we can generalize as “words”), and the range is positive whole numbers.

Some tests:

CORRECTION: AnneB shows that appearances takes sentences as the second argument if the first argument is a single character, and will find instances of the first argument in a sentence provided as the second argument, e.g.:

I thought this example was interesting. appearances seems to work differently on sentences than on words. With words, it will count each instance of a character separately within a word. With sentences, appearances seems to want the characters to be spaced apart in order to register them as being an appearance.

So even if the same letter appears twice in a row in a sentence, it won’t count that, but it will count letters on their own:

So a more correct statement is that appearances takes sequences of letters, numbers, and punctuation marks for the first argument, and all that plus sentences for the second argument, and returns a positive integer.

Exercise 2.4

One of the functions you can use is called item. Experiment with it, and then describe fully its domain and range, and what it does.

What it does: After some testing (see below) it seems that item takes a number for the first argument, and a sentence or word (or a number treated as a word) as the second argument. Then it returns a character of the second argument, or word in the sentence, that corresponds to the number of the first argument. Specifically, it returns the character of the second argument or word in a sentence that you get if you start counting characters/words from the left side of the second argument until you get to the [first argument] character. For example, if the first argument is “3” and the second argument is “654”, then item will return the third character (counting from the left) of the second argument, which is 4. Or if the first argument is 5 and the second argument is Liberty, then item will return the fifth character of the second argument (counting from the left) which is r. Or if the first argument is “4” and the second argument is “(we the people of the united states)”, then item will return “of”.

Domain: the first argument has to a positive number less than or equal to the total number of characters (if a word) or words (if a sentence) of the second argument. The second argument has to be a word (including a number which is treated as a word) or sentence.

Some tests. Format is:
Arg1, Arg2: Result.
(I did a different format cuz i did tons of testing on this one, wanted it to be more compact)

0, 0: Argument(s) not in domain.
1, 0: 0.
2, 0: Argument(s) not in domain.
0, 1: Argument(s) not in domain.
0, 2: Argument(s) not in domain.
1, 0: 0.
1, 1: 1.
1, 2: 2.
1, 3: 3.
1, 4: 4.
1, 5: 5.
2, 0: Argument(s) not in domain.
2, 1: Argument(s) not in domain.
2, 2: Argument(s) not in domain.
2, 4: Argument(s) not in domain.
2, 23: 3.
2, 32: 2.
2, 33: 3.
2, 22: 2.
2, 57: 7.
2, 75: 5.
3, 654: 4.
3, 456: 6.
1, 456: 4.
2, 456: 5.
-1, -1: Argument(s) not in domain.
w, ww: Argument(s) not in domain.
15, 12345678901234567890: 5.
9, 123456789012: 9.
4, potato: a.
5, Liberty: r.
1, (grilled cheese and tomato sammich): Argument(s) not in domain. This appears to be an issue with functions.scm
0, potato: Argument(s) not in domain.
1, potato: p.
-1, potato: Argument(s) not in domain.

the ability to handle sentences as arguments is broken in functions.scm, as mentioned before. However, I tried the regular Scheme version of item to test how it handles sentences:

1, (potato tomato): potato.
2, (potato tomato): tomato.

Note for Exercises 2.5 through 2.9

The following exercises ask for functions that meet certain criteria. For your convenience, here are the functions in this chapter: +, -, /, \<=, \<, =, >=, >, and, appearances, butfirst, butlast, cos, count, equal?, every, even?, expt, first, if, item, keep, last, max, member?, not, number?, number-of-arguments, odd?, or, quotient, random, remainder, round, sentence, sqrt, vowel?, and word.

number? does not appear to be defined in my functions.scm file.

I made a (somewhat incomplete) list of data types of various functions as an aid to answering the questions.

List of functions by number of arguments

This section lists functions by number of arguments permitted for the argument within the functions.scm file used for this chapter. I made these lists cuz it seemed like they would be useful for answering the questions that follow.

List of one-argument functions

List of two-argument functions

List of three-argument functions

Exercise 2.5

List the one-argument functions in this chapter for which the type of the return value is always different from the type of the argument.

  • count takes numbers but i guess it doesn’t treat them as numbers but as a string of characters. so it treats “666” just like “aaa”. it returns numbers. so I think count always returns things with a type different than the type of argument it takes.
  • even? only has integers in its domain and only returns boolean values, so it would count as an example of such a function.
  • number-of-arguments only has functions within its domain and only returns positive integers.
  • odd? only has integers in its domain and only returns boolean values, so it would count as an example of such a function.

Exercise 2.6

List the one-argument functions in this chapter for which the type of the return value is sometimes different from the type of the argument.

  • vowel accepts boolean values as an argument and returns boolean values. So if you provide it a boolean value and it returns a boolean value, then the type of the argument and the type of the return are the same. OTOH, if you provide it with a letter or word and it returns a boolean value, then the data types are different. So they are sometimes different.

NOTE:

So there seems to be a big difference in answers between me and AnneB. I think this may be an issue of how we interpreted the question but let’s see. AnneB says:

The ones for which the type of the return value is sometimes different from the type of the argument are:

count can take a word as an argument and return a number.

This one I can see as arguable. count seems a bit tricky to me. It takes numbers but seems to deal with them as any other character, and not as numbers. I’m not quite sure how to think about that issue, so I’ll focus on what I regard as the less ambiguous cases.

even?, number?, odd? can take a number as an argument and return a boolean.

my number? was broken so we’ll leave that aside.

re: even? and odd?, I think I am reading the question as asking “Which one-argument functions have return values of a type that is sometimes different from the type of the argument, and sometimes not” and AnneB is perhaps reading it in a different way.

even? and odd? take only numbers but return only booleans. They never return the same value as they take. So I think they always return a value different from the type of argument, not only sometimes.

number-of-arguments can take a function as an argument and return a number.

Similar logic here as with even? and odd?number-of-arguments only takes functions and always returns numbers. So it always returns a type different than the type that it accepts.

vowel? can take a word as an argument and return a boolean.

we agree that vowel? is an answer.

Exercise 2.7

Mathematicians sometimes use the term “operator” to mean a function of two arguments, both of the same type, that returns a result of the same type. Which of the functions you’ve seen in this chapter satisfy that definition?

I read this to be asking for a function that always returns a result of the same type as the two arguments.

NOTE: AnneB’s list is almost the same as mine, except that I have expt in my list and she does not.

Exercise 2.8

An operator f is commutative if f(a,b)=f(b,a) for all possible arguments A and B. For example, + is commutative, but word isn’t. Which of the operators from Exercise 2.7 are commutative?

Exercise 2.9

An operator f is associative if f(f(a,b),c)=f(a,f(f(b,c)) for all possible arguments A, B, and C. For example, * is associative, but not /. Which of the operators from Exercise 2.7 are associative?

I made a table to help me figure out whether OR was associative

❌INCOMPLETE:

✅CORRECTION: AnneB lists word as associative and I think she is right. It does not matter whether you join words a and b first and then join ab with c, or join words b and c first and then join a with bc. The word order and result is going to come out the same in either case.

End of Chapter Review

Felt pretty solid on my development of the “testing things out” skill this chapter. I think that’s kind of the point of this chapter.

Trying to think somewhat rigorously about the domain and range of functions seems valuable.

Questions

What is the correct way to describe the domain of count, given that it takes numbers as arguments but treats them as words?

My Prior Learning Efforts

As with Chapter 1, there appear to be none to speak of.

Other People’s Learning Efforts

AnneB’s notes were super helpful to refer to. It’s hard to find much stuff to refer to for Chapter 2. You start seeing stuff for Chapter 3 and later.