Laboratory Exercises For Computer Science 153

Variable Arity

Variable Arity

Goals: This lab introduces the concept of procedures with varying numbers of parameters and provides experience writing such procedures.

To motivate our discussion and provide a running example, consider what capabilities we might want for printing several results. If we need to print three values (result1, result2, result3) on a line, one approach might utilize the display and newline procedures:


(display result1)
(display result2)
(display result3)
(newline)
However, this approach is somewhat cumbersome, as we must call display for each value with newline at the end. Of course, we could package such an operation in a procedure display-3, but then we also might want procedures display-2, display-4, display-5, etc.

Instead, it would be more convenient to have a display-line operation which would print as many values as we wish on the line; we would like to define display-line so that it can be used for 1 or 2 or 3 or more values, and all would be printed on a line -- with a newline at the end.

Such a procedure can be defined in several ways, taking advantage of alternative forms of lambda expressions.

Approach 1: As a first approach, consider the following procedure:


(define display-line
    (lambda values
        (let loop ((lst values))
              (if (null? lst)
                 (newline)
                 (begin 
                     (display (car lst))
                     (loop (cdr lst))
                 )
              )
        )
    )
)
A sample run follows:

>(display-line "To" "Be" "Or" "Not" 2 "B")
ToBeOrNot2B
In this procedure, note that the symbol values after lambda is not in parentheses. Scheme interprets this syntax to indicate that all parameters to the function should be grouped together in a list. Thus, the sample applies display-line to the list ("To" "Be" "Or" "Not" 2 "B").

  1. Apply display-line to several other expressions to check what it prints. For example, try the following:
    
    (display-line "Going"  "Going"  "Gone")
    (display-line "Countdown:"  5  4  3  2  1  "Done")
    (display-line)          ;;; apply display-line to the null list
    
  2. The current version of display-line prints all text together without spaces. Modify the code, so that one space is printed between each value specified for display-line. Thus, the first sample run above should print:
    
    To Be Or Not 2 B
    

Approach 2: While the above code prints out all parameters, sometimes we might prefer to print out the items separated by a specified string. Thus, we might want to generalize display-line by giving, as its first parameter, the string to be used as a separator. Some examples might be:


>(display-line "" "going..." "going..." "gone")
going...going...gone
>(display-line "..." "going" "going" "gone")
going...going...gone
>(display-line (string #\tab) 1996 'foo 'wombat 'quux)
1996    foo     wombat  quux

In this case, display-line only makes sense if it contains a first parameter, but we cannot anticipate how many elements might appear after that. Another form of lambda expressions allows us to bind initial parameters to specific parameters, with any remaining parameters collected together in a list. This is illustrated in the following code:

(define display-line
    (lambda (separator . element-list)
        (let loop ((lst  element-list))
                (cond ((null? lst) (newline))
                      ((null? (cdr lst)) (display (car lst)) (newline))
                      (else 
                        (display (car lst))
                        (display separator)
                        (loop (cdr lst)))
                )
        )
    )
)
Here, (separator . element-list) follows lambda. In this context, the first parameter is bound to separator, while all remaining parameters are placed on a list and bound to element-list.

  1. Check the correctness of this code by running it with the above examples. Describe how the result is formatted in each case. What happens when this version of display-line is run with no parameters? What happens when only 1 parameter is given?

  2. Note that in the definition, a space was left on each side of the dot in
    (separator . element-list). What happens if these spaces are removed to yield (separator.element-list)? Explain why you think this result is obtained.

  3. Discuss why you think the condition (null? (cdr lst)) appears in this procedure.

  4. This procedure has the disadvantage that both (null? lst) and (null? (cdr lst)) will be evaluated each time loop is called recursively. Can you revise this procedure (perhaps inserting some code before the let), so that only one condition will be tested during the recursion.

The dot notation can be used to specify any number of initial values. Thus,
(first-value second-value . remaining-values) gives explicit bindings for two parameters, with any further parameters collected on a list for remaining-values.

  1. Modify the above code to include a port as the first parameter. The separator then would be a second parameter. All subsequent elements would be printed at the specified port.

Try the following exercises:
  1. Write a procedure increasing? which takes one or more numbers as parameters and returns true if those numbers are in increasing order. Here are a few test cases and their desired results:
    
    (increasing? 2) ==> #t
    (increasing? 2 6 10 24 129) ==> #t
    (increasing? 6 2 10 24 129) ==> #f
    (increasing? 2 6 10 129 24) ==> #f
    
  2. Write a procedure that finds the average of a sequence of numbers. Thus, (average 3 5 7 11 14) should return 8.
    (Note: An average makes sense only if we have at least one number.)


This document is available on the World Wide Web as

http://www.math.grin.edu/~walker/courses/153.sp00/lab-variable-arity.html

created March 22, 1997
last revised January 11, 2000