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):
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:
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:
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:
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:
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
Table 7.5 Scheme Vis-à-Vis ML and Haskell for Reception and Decomposition of Argument(s)
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 |
(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 if–then–else 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:
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) 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:
Explain what in this function definition causes the ML type inference algorithm to deduce its type as:
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) 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.
18.118.253.223