CS 341 - Survey of Programming Languages
Scheme for Java Programmers - Exercises

Index
REPL: Read-Eval-Print Loop
  1. Find an online survey of programming languages used for (1) general programming, or (2) an application area of interest to you.  Which of the most popular programming languages are interpreted?
  2. What are the advantages/disadvantages of interpreted versus compiled languages?
  3. In the preface to Essentials of Programming Languages, what does Abelson refer to as "the most fundamental idea in computer programming"?  Describe this idea in your own words.
  4. In the preface to Essentials of Programming Languages, what are Abelson's three reasons that a programmer should learn about interpreters?
Operators and Operands

Given that sqrt is Scheme's square root operator, express the following Java expressions in Scheme and give the printed evaluated value:

  1. (1 + 2) * (4 - 3)
  2. Math.sqrt(1/4 + 5/16)
  3. (1 + 2 + 3 + 4 + 5)  (Hint: How many operands can + take?)
  4. ((3 - 2) / 4 + 5 * (6 - 7))
Identifiers, Whitespace, and Comments

1. Looking through the index of R5RS, which of the non-alphanumeric characters tend to be used the most?  How are they used, i.e. what implicit meaning to they have?

2.  Are block comments possible in Scheme?  If so, how?

3. Take a complex expression from the tutorial and rewrite it on a single line.  Why is it difficult to visually comprehend?  That is, what benefit is derived from breaking the expression across lines and using proper indentation?

Fundamental Types

1. How does one test type membership in Scheme?

2. How are the Boolean values true and false denoted in Scheme?

3. Express the following Java expression in Scheme: (!false && (true || false))

4. How do numeric types differ between Scheme and Java?

5. What is the value of the following Scheme expression?: (+ (/ 1 2) (/ 1 3))

6. Suppose you have a test string called test-string (e.g. (define test-string "testing")). Assuming test-string has at least 2 characters, what is the expression that produces a string with all but the first and last characters?

7. What function tests the equality of two strings?  What's the easiest way to test case-insensitive equality of strings?

8. What functions convert numbers to strings and strings to numbers?

9. How is a newline character denoted in Scheme?

10. What is the difference between count and 'count (i.e. (quote count)) in Scheme?  What is the simplistic meaning of the single-quote?

Variables and Assignments

1. Define a variable count, initialized to 0.  Then increment the variable by 1.

Pairs and Lists

1. What is the difference between  (cons 'a 'b) and (list 'a 'b)?  How would each appear in pair notation?

2. Given (define l (list '(a b c) '(d e f) '(g h i))), how would you access each of symbols 'c through 'i in l?  For example,  'b is accessed by (car (cdr (car l))) or the abbreviated form (cadar l).

3. As in 2, demonstrate access for each of the symbols of this complex structure:  (define s '(((a b) (c . d)) ((e . f) . (g h))))

4. What were the more intuitive names for car and cdr and why were they more sensible?

5. What is the pair notation expression '((a . (b . ())) . (c . ())) equivalent in list notation?

6. Using only cons, the empty list, and symbols, show how to construct each of the following: '(c), '(a b), '((a b) (c)).  You may define variables to store and reuse values.

Lambda Expressions

1.  One way to describe a line is to make y a function of x.  The common notation is y = mx + b, where m is the slope and b is the y-intercept.  Define an unnamed function that takes x as input, and evaluates y given that m = 2 and b = 1.

2.  Define get-y to be the unnamed function of (1).  Use map to apply it to the list '(-2 -1 0 1 2).

3.  Define a function make-get-y that takes m and b as input and returns an unnamed function which takes x as input and evaluates mx + b.  Note: You're defining a function that returns a function.  That is, the body expression of the lambda expression is also lambda expression.

4. Define get-y2 to be the same function as in (2), but create the function using make-get-y of (3).    Use map to apply get-y2 to the list '(-2 -1 0 1 2).

5. A degree-2 polynomial has the form y = a*x*x + b*x + c.  Define an unnamed function that takes x as input, and evaluates y given that a = 1, b = 2, and c = 3.

6. Define polynomial to be the unnamed function of (5).  Use map to apply it to the list '(-2 -1 0 1 2).

7. Define a function make-polynomial that takes a, b, and c as input and returns an unnamed function which takes x as input and evaluates a*x*x + b*x + c

8. Define polynomial2 to be the same function as in (6), but create the function using make-polynomial of (7).    Use map to apply polynomial2 to the list '(-2 -1 0 1 2).

9. What is the difference between eval and apply?  What operands do each take and what is the result of the their application to these operands?

Control Constructs

1.  Assuming that the arguments after the if condition do not evaluate to #f, define a function my-if that behaves like if, but is defined in terms of or and and.  Test my-if with the following expressions: (my-if #t 'first 'second) (my-if #f 'first 'second)

2.  Define a recursive function last that returns the last element of a list operand.  You may assume that the list has at least one element.  Show that last works for '(1) '(1 2) and '(1 2 3 4 5).  Hint: The null? predicate is useful here.

3.  Define a recursive function factorial that takes positive integer n and evaluates to n!.  Show the evaluation of 1!, 2!, and 5!.

4.  Define a recursive function sum-of-ints that takes a positive integer n and evaluates the sum 1 + 2 + ... + n.  Show the evaluation for n = 1, 5, and 10.

5.  Define a recursive function fib that takes a non-negative integer n and returns the nth Fibonacci number.  (fib 0) is 0, (fib 1) is 1, and for all other n, (fib n) is the sum of the two preceding numbers in the Fibonacci sequence.  Hint: Use cond for these three cases.  Use map to test fib on each of the integers in the list '(0 1 2 3 4 5 6 7 8).

6.  Devise a recursive Fibonacci function fib-tr that doesn't generate a tree of recursive calls.  Rather make it tail recursive, computing from the beginning of the sequence forward until it reaches the desired number.  You will need to define an auxiliary function to pass forward the previous two numbers, the current sequence number, and the desired sequence number.  Use map to test fib-tr on each of the integers in the list '(0 1 2 3 4 5 6 7 8).

For example, if you called your auxiliary function fib-tr-aux with the parameters in the order given above, the call sequence for (fib-tr 6) might be:

   (fib-tr 6)
=> (fib-tr-aux 0 1 1 6)
=> (fib-tr-aux 1 1 2 6)
=> (fib-tr-aux 1 2 3 6)
=> (fib-tr-aux 2 3 4 6)
=> (fib-tr-aux 3 5 5 6)
=> (fib-tr-aux 5 8 6 6)
=> 8

7.  Define the logical operator nand (not and) using not and and.  Does short-circuiting evaluation occur in your implementation?  Why or why not?  Hint: You can test whether or not the second operand is evaluated by making it a display statement.

8.  Define the logical operator nor (not or) using not and and.  Does short-circuiting evaluation occur in your implementation?  Why or why not?  Hint: You can test whether or not the second operand is evaluated by making it a display statement.

9.  Define a function direction that takes a number of degrees degrees and returns a symbol as follows: -45 < degrees <= 45 returns 'right, 45 < degrees <= 135 returns 'up, 135 < degrees <= 225 returns 'left, 225 < degrees <= 315 returns 'down, and all other values have 360 added or subtracted until they map to a number (and thus symbol) within these ranges.  Use map to test direction on each of the integers in the list '(-720 0 90 180 270 720).

10.  Define a function digit-case that takes an non-negative integer and uses a case statement to return either 'even-digit, 'odd-digit, or 'not-a-single-digit accordingly.   Use map to test digit-case on the list '(0 1 2 3 4 5 6 7 8 9 10).

11.  Define function last-do that implements last of (2) using a do loop.  Show that it works for the same test cases.

12.  Do (3) using an alternative implementation fact-do that uses a do loop.  Show that it works for the same test cases.

13.  Do (4) using an alternative implementation sum-do that uses a do loop.  Show that it works for the same test cases.

14.  Do (5) using an alternative implementation fib-do that uses a do loop.  Hint: This is much like the approach for (6).  Show that it works for the same test cases.

15.  Compare and contrast begin and and.  What is the essential difference?

 

Binding Constructs

So far, we've introduced global variables with "define", and local variables with the lambda argument list.  We haven't yet shown how to introduce local variables that aren't procedural arguments.  There are three forms for doing so: "let", "let*", "letrec". 

(let ((<var1> <binding-expression1>)
      (<var2> <binding-expression2>)
      ...
      (<varN> <binding-expressionN>))
  <local-expression>) 
The binding expressions are all evaluated together in the surrounding outer variable context. Then they are assigned in some unspecified order to the local variables.  The local expression is then evaluated with respect to the new local variable environment.  It's helpful to think of the new local environment being built on top of the outer variable environment.  If one of the local variable has the same name as a variable in the surrounding environment, it shadows the outer variable.  Shadowing occurs in Java as well.  Local variables and parameter variables shadow field variables.  In many languages, locality determines scope priority.

Here are some examples:

> (let ((x 2)
        (y 3))
    (+ x y))
5
> (let ((x 2))
    (let ((x 3)
          (y (+ x 4)))
      (+ x y)))
9

Look at the latter example more closely. Why isn't the answer 10? If we wish to have the local variables be created and bound in sequence, we use the "let*" operator. "let*" shares the same syntax, but has the meaning of performing a bunch of nested single-binding "let"s.

> (let ((x 2))
    (let* ((x 3)
           (y (+ x 4)))
      (+ x y)))
10
> (let ((x 2))
    (let ((x 3))
      (let ((y (+ x 4)))
        (+ x y))))
10

"let" is more natural if you're only referring to the outer variable context. "let*" is more natural if there are non-circular dependencies between variables such that they can be bound in sequence. But what if the variables are defined in terms of one another? In Scheme, you can recursively define variables using "letrec". Here's an example (based on a R5RS example) in which two variables holding procedures are defined according to one another:

 (letrec ((even? 
            (lambda (n)
              (or (zero? n) (odd? (- n 1)))))
           (odd? 
            (lambda (n)
              (and (not (zero? n)) (even? (- n 1))))))
    (even? 42))
#t

In general, prefer the simplest binding construct according to your needs.

Equivalence

There are three general equivalence predicates in Scheme: "eq?", "eqv?",  and "equal?".  "eq?" is most discriminating and strict in its interpretation of equality.  "equal?" is the least discriminating and strict in its interpretation of equality.  Detailed description of these can be found in R5RS, but in a nutshell:

For example:

> (eqv? 'a 'a)
#t
> (eqv? '() '())
#t
> (eqv? '(a) '(a))
#f
> (equal? '(a) '(a))
#t
> (equal? '(- a (+ b 1)) '(- a (+ b 1)))
#t

You'll generally use "=" for comparing exact numbers, "eqv?" for comparing simple objects, and "equal?" for comparing complex objects.

More List Operators

Since list-processing is the forte' of Scheme and Lisp, there are many useful list operators.  Here are some basic examples:

> (length '(a b c))
3
> (append '(a b c) '(d e f))
(a b c d e f)
> (reverse '(a b c))
(c b a)
> (list-tail '(a b c d e f) 3)
(d e f)
> (list-ref '(a b c d e f) 3)
d
> (memv 'c '(a b c d e f))
(c d e f)
> (memv 'z '(a b c d e f))
#f
> (define assoc-list '((a 1) (b 2) (c 3)))
> (assv 'b assoc-list)
(b 2)
> (assv 'z assoc-list)
#f

"list-ref" and "list-tail" can be used to access list elements or tail sublists by index. "memv" can be used to check list membership. "assv" takes a key and an association list (a.k.a. a-list) of key-value pairs and either returns the relevant pair or #f if the key is not present.

Output

As we will be making use of some non-standard input procedures in our programming, we will only cover simple output here. 

> (write assoc-list)
((a 1) (b 2) (c 3))
> (write "string")
"string"
> (display "string")
string
> (write-char #\newline)

> (newline)

>

"write" will display any Scheme data in its standard representation form. Thus, a string which is written will be enclosed in double-quotes. The "display" operator will display string in their normal display representation as if each string character were written individually using "write-char". The "newline" operator simply writes a new line to the current output port. As noted earlier, a port is a basic data type. More details on input/output can be found in R5RS section 6.6.

Next Steps

We haven't covered Scheme's library exhaustively (e.g. vectors).  Nor have we covered it's semantics or implementation restrictions in rigorous detail.  What we have accomplished is a blitz introduction that should give you a good starting foundation for practice.  Learning is experiential.  Echoing Socrates, Charlie Peacock sang, "We can only possess what we experience."  Reading about Scheme is nothing like programming Scheme. 

Your real education begins as you approach problems, simple or complex, and translate your solutions into Scheme code.  At first, there will be a disconnect between how you think and the how you program Scheme.  As all programming languages, Scheme cannot change what it is.  However, your mind can change and will learn to translate thoughts into a very different paradigm than it is used to.  This stretching exercise of the mind will give you a broader view of programming, a great comfort with recursion, and, if you're open to it, a yearning for functional programming.  

Online Resources

© 2003 Todd Neller