University of Virginia, Department of Computer Science
CS655: Programming Languages, Spring 2001

Problem Set 1: Higher-Order Functions - Selected Answers

1. Mergering

a. Write a Scheme procedure listadder that combines two lists by adding their elements. For example, (listadder '(3 1 3) '(3 4 2)) should produce (6 5 5). (Don't worry about making your procedure work for lists of different length.)

Answer: The simplest answer (provided by Michael Deighan, Yuangang Doris Cai and Weilin Zhong):
(define (listadder list1 list2)
  (if (null? list1) '()
      (cons (+ (car list1) (car list2))
	    (listadder (cdr list1) (cdr list2)))))
b. Write deeplistadder that works on nested lists also. For example, (deeplistadder '((1 2 (3 4)) 4) '((1 2 (3 4)) 6)) should produce ((2 4 (6 8)) 10).

Answer: Doris Cai and Weilin Zhong's answer:
(define (deeplistadder list1 list2)
  (if (null? list1) 
      list1
      (if (list? (car list1))
	  (cons (deeplistadder (car list1) (car list2))
		(deeplistadder (cdr list1) (cdr list2)))
	  (cons (+ (car list1) (car list2))
		(deeplistadder (cdr list1) (cdr list2))))))
c. The deeplistadder procedure could be defined using a more general procedure we can call deeplistmerge. Define deeplistmerge so that deeplistadder could be defined as
        (define (deeplistadder list1 list2)
	    (deeplistmerge + list1 list2))
Answer: Doris Cai and Weilin Zhong's answer:
(define (deeplistmerger func list1 list2)
  (if (null? list1) 
      list1
      (if (list? (car list1))
	  (cons (deeplistmerger func (car list1) (car list2))
		(deeplistmerger func (cdr list1) (cdr list2)))
	  (cons (func (car list1) (car list2))
		(deeplistmerger func (cdr list1) (cdr list2))))))

d. Define a function deeplistmaxer that combined the lists by choosing the higher element. For example, (deeplistmaxer '(3 1 3) '(3 4 2)) should be (3 4 3).

Answer: Dana Wortman and Tom Sabanosh's answer:
(define (deeplistmaxer list1 list2)
  (deeplistmerger (lambda (x y) (if (> x y) x y)) list1 list2))
e. Try to write the deeplistmerge function using some other programming language you know well.

(1) For the language you choose, is it possible to write deeplistmerge?

(2a) If so, is it harder or easier than it was to do using Scheme? What properties of the languages make it harder or easier?

(2b) If not, is the language you choose a universal programming language? (Reconcile any paradoxes in your answers.)

Answer: No one selected a language in which it is easier to write than in Scheme. One example of a language in which it is easier to write (at least cosmetically) is ML. We will seem more about ML when we cover type inference later in the class. We could define deeplistmerger in ML using:
fun deeplistmerge (f, [], []) = []
  | deeplistmerge (f, [a]::list1, [b]::list2) = deeplistmerge (f, a, b) @ deeplistmerge(f, list1, list2)
  | deeplistmerge (f, a::list1, b::list2) = [f(a, b)] @ deeplistmerge(f, list1, list2)
Most of you attempted to implement deeplistmerge in C or C++. This required a fair bit of effort and ingenuity. Some things you should have (and most of you did) notice about why this is hard: In Java, it is possible to use the reflection classes (java.lang.reflect) to create a more general deeplistmerge routine (Haiyong Wang and Elisabeth Strunk did this).

Brian Clarke produced this solution in Perl (excerpted):

@output=&deeplistmerge(sub{$_[0]+$_[1];},$list0,$list1);
print "deeplistadder:\n",join(" ",@output),"\n\n";

sub deeplistmerge {
  ...
      push(@out,&$f($list0[$i],$list1[$i]));
Of course, all of the languages you used are universal, so it must be possible. The question is what it means --- we can produce a function in any universal language that has the same input-output (that is it produces the same result for the same parameters) behavior as any function we can write in Scheme. The easiest way to do this is to write a Scheme interpreter in the other language. Writing a Scheme interpreter in C is pretty easy.

Here's Chris Taylor and Mike Tashbook's answer:

While it would be extremely difficult to write a function like deeplistmerge using Java, Java is a universal programming language. Using the proper encoding scheme, any computation is possible. One example of this can be seen in my Master's thesis, where I implemented a Turing Machine simulator using Java; given the proper set of instructions, this Turing Machine simulator can perform any specified computation. My simulator converts these instructions into simpler Java operations, demonstrating that Java can indeed be used to perform any possible computation.

2. Whiling Away While

a. I. M. Peirative complains that Scheme can't possibly be a useful language, since it does not have a while loop, and everyone knows you can't write any useful programs without having a while loop.

Show I. M. that he is wrong by defining a procedure in scheme that provides the power of a while loop. Using your procedure, I. M. should be able to define factorial using:

(define (factorial n)
  (while (lambda (x) (<= x n))  ;;; the while predicate
	 (lambda (x) (+ x 1))   ;;; the increment function
	 (lambda (ival accum) (* ival accum)) ;;; the loop body
	 1  ;;; initial i value
	 1)) ;;; initial accum value
Your definition should be in the form
(define (while pred inc body val accum) ???).
Answer: Almost everyone's answer:
(define (while pred inc body val accum)
  (if (pred val)
      (while pred inc body (inc val) (body val accum))
      accum))
Another possibility:
(define (while pred inc body val accum)
  (if (pred val)
      (body val (while pred inc body (inc val) accum))
      accum))
The first answer is tail recursive. This means, the run-time does not need to build up a stack. The second answer is not tail recursive --- it is (perhaps) more elegant, but requires more space to execute.

Alyssa P. Hacker points out that she can use your definition of while to define for so her friend Phor Tran is happy also. She defines for:

   (define (for start end body accum)
     (while (lambda (x) (<= x end))
	    (lambda (x) (+ x 1))
	    body
	    start
	    accum))

b. Show how factorial can be defined using for.

Answer: Almost everyone got:
(define (for start end body accum)
  (while (lambda (x) (<= x end))
	 (lambda (x) (+ x 1))
	 body
	 start
	 accum))
I. M. is impressed with this, but Pasquale A. Holick claims that while while and for are all good and dandy, but any real programming language has repeat ... until.

c. Show Pasquale that you can provide repeatuntil also. Your definition of repeatuntil should use while, and should not need a recursive call. Pasquale wants to use your definition to define factorial using:

   (define (factorial n)
     (repeatuntil (lambda (x) (> x n))  ;;; the until predicate (stop when true)
		  (lambda (x) (+ x 1))   ;;; the increment function
		  (lambda (ival accum) (* ival accum)) ;;; the loop body
		  1  ;;; initial i value
		  1)) ;;; initial accum value
Answer: All we have to do is negate the predicate:
(define (repeatuntil pred inc body val accum)
  (while (lambda (x) (not (pred x))) inc body val accum))
d. Use the power of higher-order functions to invent and define a new control form. Demonstrate that your control form is useful by showing how it can be used to elegantly and concisely express a program.
Answer: Most of the new control forms weren't very useful or interesting. There is a good reason for this --- despite 40 years of language design, almost every control form in every language is still nearly identical to something in Algol 60. The only real advances have been in exception handling and method dispatching (which are only arguably control forms).

Its not clear if there is some reason why the number of truly useful control forms is so low, or its just that language designers have not been creative enough to come up with good new ones. Common LISP has dozens of control forms (e.g., when, unless, cond, if, case, block, return-from, return, loop, do, do*, dolist, dotimes, for, etc.). This is one of the reasons it is very hard to read and understand someone else's Common LISP program.

Here's Chris Taylor and Mike Tashbook's answer (slightly adapted):

(define (threeway val com bless bequ bgreat)
  (if (< val com)
      bless
      (if (= val com) 
	  bequ
	  bgreat)))

(define (binary-tree-search val tree)
  (if (null? tree)
      (error "Not found")
      (threeway val
		(car tree)
		(tree-search val (car (cdr tree)))
		val
		(tree-search val (car (cdr (cdr tree)))))))


CS 655 University of Virginia
Department of Computer Science
CS 655: Programming Languages
David Evans
evans@virginia.edu