7.10 Variable-Length Argument Lists in Scheme

Thus far in our presentation of Scheme we have defined functions where the parameter list of each function, like any other list in Scheme, is enclosed in parentheses. For example, consider the following identity function, which can accept an atom or a list (i.e., it is polymorphic):

A set of nine code lines in Scheme for an identity function.
Description

The second and third cases fail because f is defined to accept only one argument, and not two and three arguments, respectively.

Every function in Scheme is defined to accept only one list argument. We did not present Scheme functions in this way initially because most readers are probably familiar with C, C++, or Java functions that can accept one or more arguments. Arguments to any Scheme function are always received collectively as one list, not as individual arguments. Moreover, Scheme, like ML and Haskell, does pattern matching from this single list of arguments to the specification of the parameter list in the function definition. For instance, in the first invocation just given, the argument 1 is received as (1) and then pattern matched against the parameter specification (x); as a result, x is bound to 1. In the second invocation, the arguments 1 2 are received as the list (1 2) and then pattern matched against the parameter specification (x), but the two cannot be matched. Similarly, in the third invocation, the arguments 1 2 3 are received as the list (1 2 3) and then pattern matched against the parameter specification (x), but the two cannot be matched. In the fourth invocation, the argument ’(1 2 3) is received as the list ((1 2 3)) and then pattern matched against the parameter specification (x); as a result, x is bound to (1 2 3).

Scheme, like ML and Haskell, performs pattern matching from arguments to parameters. However, since lists in ML and Haskell must contain elements of the same type (i.e., homogeneous), the pattern matching in those languages is performed against the arguments represented as a tuple (which can be heterogeneous). In Scheme, the pattern matching is performed against a list (which can be heterogeneous). This difference is syntactically transparent since both lists in Scheme and tuples in ML and Haskell are enclosed in parentheses.

Even though any Scheme function can accept only one list argument, because a list may contain any number of elements, including none, any Scheme function can effectively accept any fixed or variable number of arguments. (A function capable of accepting a variable number of input arguments is called a variadic function.11) To restrict a function to a particular number of arguments, a Scheme programmer must write the parameter specification, from which the arguments are matched, in a particular way. For instance, (x) is a one-element list that, when used as a parameter list, forces a function to accept only one argument. Similarly, (x y) is a two-element list that, when used as a parameter list, forces a function to accept only two arguments, and so on. This is the typical way in which we have defined Scheme functions:

A set of 16 code lines for defining a function in Scheme.
Description

By removing the parentheses around the parameter list in Scheme, and thereby altering the pattern from which arguments are matched, we can specify a function that accepts a variable number of arguments. For instance, consider a slightly modified definition of the identity function, and the same four invocations as shown previously:

A set of nine code lines in Scheme for an identity function.
Description

In the first invocation, the argument 1 is received as the list (1) and then pattern matched against the parameter specification x; as a result, x is bound to (1). In the second invocation, the arguments 1 2 are received as the list (1 2) and then pattern matched against the parameter specification x; as a result, x is bound to the list (1 2). In the third invocation, the arguments 1 2 3 are received as the list (1 2 3) and then pattern matched against the parameter specification x; x is bound to the list (1 2 3). In the fourth invocation, the argument ’(1 2 3) is received as the list ((1 2 3)) and then pattern matched against the parameter specification x; x is bound to ((1 2 3)). Thus, now the second and third cases work because this modified identity function can accept a variable number of arguments.

A programmer in ML or Haskell can decompose a single list argument in the formal parameter specification of a function definition using the :: and : operators, respectively [e.g., fun f (x::xs, y::ys) = ... in ML]. A Scheme programmer can decompose an entire argument list in the formal parameter specification of a function definition using the dot notation. Note that an argument list is not the same as a list argument. A function can accept multiple list arguments, but has only one argument list. Therefore, while ML and Haskell allow the programmer to decompose individual list arguments using the :: and : operators, respectively, a Scheme programmer can only decompose the entire argument list using the dot notation.

The ability to decompose the entire argument list (and the fact that arguments are received into any function as a single list) provides another way for a function to accept a variable number of arguments. For instance, consider the following definitions of argcar and argcdr, which return the car and cdr of the argument list received:

A set of seven code lines in a Scheme programmer with definition of a r g c a r and a r g c d r.
Description
Continuation of the code in a Scheme programmer with definition of a r g c a r and a r g c d r, consisting of 25 lines.
Description

Here, the dot (.) in the parameter specifications is being used as the Scheme analog of :: and : in ML and Haskell, respectively, albeit over an entire argument list rather than over an individual list argument as in ML or Haskell. Again, the dot in Scheme cannot be used to decompose individual list arguments:

A set of four code lines in Scheme consisting of period.
Description

Again, though transparent, Scheme, like ML and Haskell, also does pattern matching from arguments to parameters. However, in ML and Haskell, individual list arguments can be pattern matched as well. In Scheme, functions can accept only a single list argument, which appears to be restrictive, but means that Scheme functions are flexible and general—they can effectively accept a variable number of arguments. In contrast, any ML or Haskell function can have only one type. If such a function accepted a variable number of parameters, it would have multiple types. Tables 7.4 and 7.5 summarize these nuances of argument lists in Scheme vis-à-vis ML and Haskell.

Table 7.4 Scheme Vis-à-Vis ML and Haskell for Fixed- and Variable-Sized Argument Lists

A table listing the argument lists for Scheme, M L, and Haskell.
Description

Table 7.5 Scheme Vis-à-Vis ML and Haskell for Reception and Decomposition of Argument(s)

A table listing parameter reception, single list arg decomposition, and examples for Scheme, M L, and Haskell.
Description

Conceptual Exercises for Chapter 7

Exercise 7.1 Explore numeric division in Java (i.e., integer vis-à-vis floating-point division or a mixture of the two). Report your findings.

Exercise 7.2 Is the addition operator (+) overloaded in ML? Explain why or why not.

Exercise 7.3 Explain why the following ML expressions do not type check:

(a) false andalso (1 / 0);

(b) false andalso (1 div 0);

(c) false andalso (1 / 2);

(d) false andalso (1 div 2);

Exercise 7.4 Explain why the following Haskell expressions do not type check:

(a) False && (1 / 0)

(b) False && (div 1 0)

(c) False && (1 / 2)

(d) False && (div 1 2)

Exercise 7.5 Why does integer division in C truncate the fractional part of the result?

Exercise 7.6 Languages with coercion, such as Fortran, C, and C++, are less reliable than those languages with little or no coercion, such as Java, ML, and Haskell. What advantages do languages with coercion offer in return for compromising reliability?

Exercise 7.7 In C++, why is the return type not considered when the compiler tries to resolve (i.e., disambiguate) the call to an overloaded function?

Exercise 7.8 Identify a programming language suitable for each cell in the following table:

Type safe

Type unsafe

Statically typed

Dynamically typed

Exercise 7.9

(a) Investigate duck typing and describe the concept.

(b) From where does the term duck typing derive?

(c) Is duck typing the same concept as dynamic binding of messages to methods (based on the type of an object at run-time rather than its declared type) in languages supporting object-oriented programming (e.g., Java and Smalltalk)? Explain.

(d) Identify three languages that use duck typing.

Exercise 7.10 Suppose we have an ML function f with a definition that begins: fun f(a:int, b, c, d, e)= .... State what can be inferred about the types of b, c, d, and/or e if the body of the function is each of the following ifthenelse expressions:

(a) if a < b then b+c else d+e

(b) if b < c then d else e

(c) if b < c then d+e else d*e

Exercise 7.11 Given a function mystery with two parameters, the SML-NJ environment produces the following response:

A function reads as follows. v a l mystery equals f n, colon i n t list minus greater than i n t list minus greater than i n t list.

List everything you can determine from this type about the definition of mystery as well as the ways which it can be invoked.

Exercise 7.12 Consider the following ML function:

A function reads as follows. f u n f, left parenthesis, g comma h, right parenthesis, equals g, left parenthesis, h, left parenthesis, g, right parenthesis, right parenthesis, semicolon.

(a) What is the type of function f?

(b) Is function f polymorphic?

Exercise 7.13 Consider the following definition of a merge function in ML:

A set of five code lines in M L with the definition of a merge function.
Description

Explain what in this function definition causes the ML type inference algorithm to deduce its type as:

A function definition is as follows: v a l merge equals f n colon i n t list asterisk i n t list yields i n t list.

Exercise 7.14 Explain why the ML function reverse (defined in Section 7.9) is polymorphic, while the ML function sum (also defined in Section 7.9) is not.

Exercise 7.15 Consider the following Scheme code:

A set of six code lines in a Scheme code.
A set of six code lines in a Scheme code.
Description

(a) Is this an example of function overloading or overriding?

(b) Run this program in DrRacket with the language set to Racket (i.e., #lang racket). Run it with the language set to R5RS (i.e., #lang r5rs). What do you notice?

(c) Is function overriding possible without nested functions?

(d) Does JavaScript support function overloading or overriding, or both? Explain.

Exercise 7.16 Consider the following two ML expressions: (x+y) and fun f x y = y;. The first expression is an arithmetic expression and the second expression is a function definition. Which of these expressions involves polymorphism and which involves overloading? Explain.

11. The word variadic is of Greek origin.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.253.223