Programming Languages (CSC-302 98S)

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]


Examination 2: Functional Programming

Problems

Answer four of the following six questions. Each question is worth 25 points. While the problems have equal value, they are not of equal difficulty. Some will take you longer, some will take you less time.

1. Testing Scheme Routines

Suppose you've been hired to test a Scheme function which is documented as follows:

;;; (positionOf val lst)
;;;   Given a number, val, and a sorted list of numbers,
;;;   lst, determine the position of val in lst.
;;;   If the value is in the list, returns the position of the
;;;     first appearance of the value.
;;;   If the value is not in the list, returns 0.
;;; Preconditions:
;;;   The list must be sorted in increasing order.
;;;   All elements of the list must be numbers.
;;;   The value must be a number.
;;; Postconditions:
;;;   The appropriate position is returned (as described above).
;;;   The list is not modified.
;;; Examples:
;;;   (positionOf 1 '(1 5 11)) => 1
;;;   (positionOf 2 '(1 1 2 3)) => 3
;;;   (positionOf 3 '(2 3 3 3) => 2
;;;   (positionOf 2 '(1 5 11)) => 0
;;;   (positionOf 1 '()) => 0
;;;   (positionOf 1 '(3 2 1)) => unspecified, may crash
;;;   (positionOf 1 '(a 1 b)) => unspecified, may crash
;;;   (positionOf 1 '(1 a b)) => unspecified, may crash

You may assume that the people who wrote positionOf were working in good faith. That is, they tried to create a working function, but may have failed nonetheless because of inadvertent errors.

Write a Scheme predicate, testPositionOf?, that performs a reasonable set of tests on the positionOf function and returns true if it appears that the function works correctly and false otherwise. Your predicate should not return true if it is likely that positionOf fails to work as advertised nor should it return false if it is likely that positionOf is likely to work correctly. Your goal is only to check that it correctly identifies the position of a value. You need not the other postconditions.

You may rely on the existence of "reasonable" helper functions (such as nints). Just make sure to document clearly what you expect the helper functions to do.

Since you are working under time pressure, I will not expect a perfectly thorough or working answer, simply a reasonable one.

Everyone who took CS223 (which should be most of you) should have known this answer, since it's a key part of a chapter of Bentley that we discussed (remember that chapter on binary search, testing binary search, and loop invariants). However, few of you seemed to recall it. Those who didn't recall it should still have been able to model their tests on the one from assignment five. In particular, it is important to test different size lists. In addition, it is important to test the case that the element isn't in the list as well as the case that it is in the list. For "not in the list", you should not just check "smaller than the front" and "larger than the back".

For these purposes, it seems sufficient to test list sizes from 0 to some reasonable number (perhaps 8, perhaps sixteen) and to test a search for each position in the list and between each two positions in the list (as well as before the beginning and after the end).

To make it easy to check whether the search is successful, I'll build a somewhat regular list. In particular, I'll build a list of even integers starting with two. That way, an even number is at position N/2 and odd numbers aren't in the list. To handle the different length lists, I'll use a helper function. The disadvantage of this solution is that it fails for a function that simply returns N/2 for even numbers and 0 for odd numbers. However, we could use a couple of variants to handle such cases. Since we're assuming that the people who gave us the function are working in good faith, such cases may not be necessary.

First, a helper function that checks whether the function returns the correct position when looking for a value in the list.

;;; Check if a positionOf function finds the correct position of
;;; a number in a list of even numbers starting with two.  If the
;;; number is odd, it shouldn't be in the list.  If the number is
;;; even and the length of the list is less than or equal to N/2,
;;; then it should be at position N/2.
(define (testParticularPosition fun num lst)
  (if (odd? num)
      (equal? 0 (fun num lst))
      (equal? (/ num 2) (fun num lst))))

Next, a helper function that checks every position in the list.

;;; Check if a positionOf function finds the correct position of
;;; every number between 1 and 2N+1 in the list (2 ... 2N).
(define (testAllPositions fun N)
  (let ((lst (evens N)))
    (myand (map (lambda (num) (testParticularPosition fun num lst))
                (nints (+ (* 2 N) 1) 1)))))

Finally, our test function. It checks a variety of sizes of lists. Note that we use map on a list of integers to test the different size lists and then we use "and" the results together.

;;; Check if a positionOf function finds the correct position of
;;; every number between 1 and 2N+1 in every list size between
;;; 0 and 16.
(define (testPositionOf? fun)
  (myand (map (lambda (len) (testAllPositions fun len))
              (nints 17 0))))

In order for this to work correctly, we need the following functions defined.

;;;   (nints n start):
;;;     create the list of n consecutive integers starting with start
;;;   (evens n):
;;;     create the list of n consecutive even numbers starting with 2
;;;   (myand lst):
;;;     "and" all the elements of a list of booleans

The definitions of all three are relatively straightforward.

;;; Create a list of n consecutive integers starting with start
(define (nints n start)
  (if (= n 0) nil
      (cons start (nints (- n 1) (+ start 1)))))

;;; Create a list of n consecutive even integers starting with 2.
;;; If n is odd, creates a list of n consecutive odd integers.
(define (evens n)
  (letrec ((evens-from (lambda (n start)
             (if (= n 0) nil
                 (cons start (evens-from (- n 1) (+ start 2)))))))
     (evens-from n 2)))

;;; "And" all the elements of a list of booleans.  If the list is
;;; empty, return true (#t).
(define (myand lst)
  (if (null? lst) #t
      (and (car lst) (myand (cdr lst)

Note that this isn't an ideal solution, as it doesn't check lists with duplicates. We might instead build a list of pairs, such as

(1 1 3 3 5 5 7 7 ...)

Then the appropriate place for each element is the value of the element.

For those interested in testing their own solutions, here's a positionOf function to test. Note that I've used nested ifs when a cond would probably be somewhat better.

;;; Find the position of a number in a sorted list.  Still a linear
;;; time method, but stops when it goes too far.
(define (positionOf num lst)
  ; Base case: empty list.  The number can't be there.
  (if (null? lst) 0
  ; Base case: number is at the start of the list.  Return 1.
  (if (= num (car lst)) 1
  ; Base case: number is too small.  Return 0
  (if (< num (car lst)) 0
  ; Recursive case: look for the number in the rest of the list
      (let ((check (positionOf num (cdr lst))))
         ; If it's not in the rest of the list, give up
         (if (= check 0) 0
         ; If it is in position X in the rest of the list, it's at
         ; position X+1 in the whole list
             (+ check 1)))))))

Here's one to test that has a slight bug.

(define (badPositionOf num lst)
  ; Base case: empty list.  The number can't be there.
  (if (null? lst) 0
  ; Base case: single element list and the number is too small.  It
  ; can't be there.
  (if (and (null? (cdr lst)) (<= num (car lst))) 0
  ; Base case: number is at the start of the list.  Return 1.
  (if (= num (car lst)) 1
  ; Base case: number is too small.  Return 0
  (if (< num (car lst)) 0
  ; Recursive case: look for the number in the rest of the list
      (let ((check (badPositionOf num (cdr lst))))
         ; If it's not in the rest of the list, give up
         (if (= check 0) 0
         ; If it is in position X in the rest of the list, it's at
         ; position X+1 in the whole list
             (+ check 1))))))))

2. Removing Copies

Often, it is useful to remove all copies of a value from a list of values. Scheme includes such a function as one of its basic functions. Nonetheless, I would like you to write such a function for yourself. Write a function (removeAllCopies val lst) that returns a version of lst with all elements equal to val removed. Note that some or all values in the list may not be numbers.

Everyone who did this problem did a fairly good job. Some seemed to think that they needed to cons nil with the rest of the list in the "it's at the front" case, but that's clearly wrong. Some used = instead of equal?, but I didn't consider that a real problem.

;;; Remove all copies of a value from a list of values.  Written
;;; to handle arbitrary kinds of values.
(define (removeAllCopies val lst)
  (if (null? lst) nil
      (let ((first (car lst))
            (rest (removeAllCopies val (cdr lst))))
        (if (equal? first val) rest
            (cons first rest)))))

3. Triangular Numbers

Without using recursion or loops, write a Haskell function, triangular that returns the series of triangular numbers,

1 3 6 10 15 21 ...

You may use lambda abstraction, compose, head, intsfrom, map, select, sum, take (Haskell's firstn), tail, and other similar functions.

Make sure you indicate the type of your function.

Many of use missed the fact that triangular produces an infinite list. In effect, triangular takes no parameters. Its type is therefore "list of integers" which we represent as [Int].

triangular :: [Int]

Since I disallowed recursion, the most reasonable thing to do was to simply map a function that computes the nth triangular number onto the list of integers. Here's that solution. Note that to compute the nth triangular number, you simply sum the numbers from 1 to n.

triangular = map (\n -> sum (take n (intsfrom 1))) (intsfrom 1)

There is also an elegant recursive solution that involves using the previous result (since the nth triangular number is the (n-1)st triangular number + n).

triangular = 1 : (sumpairs triangular (intsfrom 2))

This requires a helper function to add pairs of numbers, one from each list.

sumpairs :: [Int] -> [Int] -> [Int]
sumpairs (x:xs) (y:ys) = (x + y) : (sumpairs xs ys)

4. Binary Trees

Define a general binary tree datatype in Haskell, including appropriate type constructors. Then write a function that converts binary trees to lists. That is, your function should take a binary tree as input and create a list of the elements in the tree. You can choose the traversal order for the conversion function.

Note that an answer to this could be found in the Gentle Introduction to Haskell that I recommended you read. This is a variant.

I've chosen to have leaves that include no values, so the only values are in the interior of the tree. This makes my life a little bit easier.

data BinaryTree a = Leaf
                  | Node a (BinaryTree a) (BinaryTree a)

For traveral, I'll take advantage of recursion and do a depth-first, preorder, left-to-right traveral. (Many of you neglected to describe which traversal order you were using.) Note that ++ is the Haskell concatenation operator.

treeToList :: (BinaryTree a) -> [a]
treeToList Leaf = ()
treeToList (Node val left right) =
  [val] ++ (treeToList left) ++ (treeToList right)

5. Functional Programming in Pascal

In the start of chapter 10, Louden suggests that it's possible to do "functional" programming in Pascal. However, much of what he calls "functional" is simply recursive. Could one convince a functional programmer trained in Scheme or Haskell that Pascal is functional? If so, how? If not, why not?

There are a number of aspects to functional programming. While the application of functions is certainly important, the use of functions as "first class values" is also important. While some version of Pascal do permit the use of functions as parameters to other functions, Pascal does not permit you to return functions from functions. So, for example, it would be impossible to write compose in Pascal.

Some of you noted that Pascal relies on side effects for much of its computation. Nonetheless, it is certainly possible to write a Pascal program without side effects (as long as you're willing to do without assignment). In addition, there are many functional languages (Scheme included) that permit side effects.

Pascal also lacks facilities to return structured types, does not include garbage collection or symbolic values, and does not permit lambda abstractions.

Note that p. 365 of Louden contains many similar arguments.

6. Evaluation and Application

Scheme includes eval and apply functions that make it possible for programmers to build and evaluate expressions. For example,

> (define exp '(+ 2 3))
> exp
(+ 2 3)
> (exp)
Error: attempt to apply non-procedure (+ 2 3)
> (eval exp)
5
> (define nums '(2 3 1 5))
> nums
(2 3 1 5)
> (+ nums) ; remember that + is "sum"
Error in +: (2 3 1 5) is not a number.
> (apply + nums)
11
> (define (oddlist a b) (list a b b a))
> (oddlist 'alpha 'beta)
(alpha beta beta alpha)
> (define ab '(alpha beta))
> (define exp2 '(oddlist ab))
> exp2
(oddlist ab)
> (eval exp2)
Error: incorrect number of arguments to #<procedure oddlist>.
> (define exp3 '(apply oddlist ab))
> exp3
(apply oddlist ab)
> (eval exp3)
(alpha beta beta alpha)

Haskell appears to include no explicit equivalent to either eval or apply. This suggest either that such functions are unnecessary in Haskell or that it would be impossible to include them in Haskell. Which do you think is the case, and why?

Because Haskell is curried, we don't need apply because simply writing a function and its arguments gives us application (we need apply in Scheme because we have functions that we want to apply to multiple parameters). For example, to apply fun to val1 and val2, we could simply write fun val1 val2. However, if val1 and val2 are in a list, we need to do something a little bit more sophisticated (but certainly possible).

It would be difficult if not impossible to write eval because eval cannot be typed. What kind of value does it return? You can't just say "some type". For example, is (eval whatever) + 2 a legal expression? Similarly, it's not clear what the argument type for eval is. Presumably, it's something like Expression, but what are expressions?

Many of you said "Haskell is lazy; neither fits in the context of lazy evaluation." That is not a clear argument nor does it seem correct. eval exp is simply an expression whose result is the result of evaluating exp. If the "eval expression" is not needed, then the evaluation never needs to be done.

A number of you suggested that the role of lists is quite different in the two languages and that eval corresponded to Scheme's notion of list and not Haskells. I would tend to agree with that assertion.


[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]

Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

Source text last modified Wed Apr 29 10:12:05 1998.

This page generated on Wed Apr 29 10:16:54 1998 by SiteWeaver.

Contact our webmaster at rebelsky@math.grin.edu