Chapter 6
Functions
Parameter Lists
The parameter list of a function describes the number and types of the arguments that the function accepts, and the number and types of the values it returns.
The parameter list of a generic function is used to define the overall protocol of the generic function. It constrains the methods that may be added to the generic function, through the parameter list congruency rules described on page 93. It may also specify that calls to the generic function may contain any keyword arguments.
The parameter list of a method specifies the types of arguments to which the method is applicable, and declares local bindings to which those arguments will be bound during the execution of the body of the method. It may also declare the return value types of the method.
Kinds of Parameters
Dylan parameter lists support required parameters, rest parameters, keyword parameters, and sometimes a next-method parameter. They also may include return value declarations.
The complete syntax of parameter lists is given
in Methods
on page 426.
Required parameters correspond to arguments that must be supplied when a function is called. The arguments are supplied in a fixed order and must appear before any other arguments.
Each required parameter may be a name or a name specialized by a type. Specifying a type declares that supplied argument must be a general instance of that type.
A rest parameter allows a function to accept an unlimited number of arguments.* After the required arguments of a function have been supplied, any additional arguments are collected in a sequence, which is passed as the value of the rest parameter. This sequence may be immutable, and it may or may not be freshly allocated. The types of rest parameters cannot be declared.
Keyword parameters correspond to arguments that are optional and may be given in any order. Symbols are used among the arguments to guide matching of arguments to parameters. These symbols are usually written in keyword syntax and so they are known as keywords. Keyword arguments can only be supplied after all required arguments are supplied. Keyword parameters may be specialized, restricting which values may be supplied for them. Keyword parameters may also be given default values to be used when the caller does not supply a value.
Required parameters come first in the
parameter list, followed by the rest parameter, if any, and then the keyword parameters, if
any. A rest parameter is indicated by the token #rest
followed by the name of
the parameter. Keyword parameters are indicated by the token #key
followed by
the keyword parameter specifiers, optionally followed by the
token #all-keys
.
If #rest
and #key
are used in the same parameter
list, #rest
must come first. The rest parameter will be bound to a sequence
containing all the keyword arguments and their corresponding values.
A next-method parameter is indicated by the token #next
,
followed by the name of the parameter. It is not normally necessary to specify a next-method
parameter explicitly. If a next-method parameter is not specified by the
programmer, define method
inserts one with the
name next-method
. If an explicit next-method parameter is given, it must come
after the required parameters and before the rest and keyword parameters. Details of using
next-method are given in Calling Less Specific
Methods
on page 98.
Kinds of Parameter Lists
Each function (generic function or method) has an argument passing protocol specified by
its parameter list. The argument passing protocol for a method must be compatible with the
argument passing protocol of any generic function to which it is added, as described
in Parameter List Congruency
on page 93.
The argument passing protocol of a function can be described in one of the following ways:
- A function is said to require a fixed number of arguments
if its parameter list does not specify either
#rest
or#key
. - A function is said to accept keyword arguments if its
parameter list specifies
#key
. The parameter list could also specify#rest
if it is a method, but not if it is a generic function. - A function is said to accept all keyword arguments if its
parameter list specifies
#all-keys
in addition to#key
. - A function is said to accept a variable number of arguments
if its parameter list specifies
#rest
but does not specify#key
. (Note: If the parameter list specifies#key
in addition to#rest
it is not said to accept a variable number of arguments.)
A method that accepts keyword arguments is said to recognize
the keywords mentioned in its parameter list. (A method may, of course, mention them in the
parameter list and then ignore their values. It is still said to recognize them.) It is
possible for a method to accept keyword arguments in general but not recognize any
particular keywords; it does this by specifying #key
without any subsequent
keyword parameters.
If a generic function that accepts keyword arguments mentions any specific keyword arguments in its parameter list, these are the mandatory keywords of the generic function. Every method added to the generic function must recognize these keywords.
A function may accept all keyword arguments by specifying #all-keys
in its
parameter list.
When a function that accepts keyword arguments is called, it is said to permit a keyword argument in the call if one of the following is true:
- The function is a method that recognizes the keyword.
- The function is a generic function and the keyword is recognized by any of the applicable methods of the call.
- The function accepts all keyword arguments.
- The function is a generic function and any of the applicable methods of the call accepts all keyword arguments.
If a function that accepts keyword arguments is called, it will
signal an error if called with a keyword argument that it does not permit, or if the
arguments following the required arguments are not keyword/value pairs. This is true even if
the function specifies #rest
.
If a method is called via a generic function or via next-method (rather than directly), the method itself does not check whether it received any keyword arguments it does not permit, nor does it check that the arguments following the required arguments are keyword/value pairs. This check is performed by the generic function or next-method, and is made relative to the call as a whole, not relative to an individual method or the methods remaining to be called.
A call to a function may supply the same keyword argument more than once. When this is done, the leftmost keyword/value pair is used.
Specializing Required Parameters
When you define a generic function or method, you may specify the types of the arguments appropriate for the generic function or method. This is called specializing the generic function or method, or specializing the parameters of the generic function or method.
The following example defines a method specialized on <number>
. The
method will be applicable when double
is called on a general instance
of <number>
.
define method double (thing :: <number>) thing + thing; end method;
Specialization constrains the values that may be passed as the value of a parameter. The function can be called only with arguments that are instances of the specializers of the corresponding parameters.
Specialization is useful in three ways:
- It makes the intent of the program clear. It indicates to the compiler and to anyone reading the code that an error is signaled if an argument is not of the specializer type.
- It allows the compiler to perform additional optimizations.
- It is used to control method dispatch. By defining methods on the same generic function with different specializers, you can define behavior applicable to different sets of types. A generic function chooses among its methods on the basis of the methods' specializers. The generic function chooses the method whose specializers most closely match the types of the arguments.
Syntactically, specializers are operands. These operands are executed once when the function is created. They are not re-executed each time the function is called. The value of the operand must be a type.
It is most common for specializers to be constant module bindings or calls to a built-in
type constructor such as singleton
, limited
,
or type-union
.
There is a convenient syntax for singleton specializers, which is equivalent to explicitly
calling singleton
in the current lexical scope.
Keyword Parameters
The syntax of a keyword parameter is:
[ keyword ] name [ ::
operand ] [ =
expression ]
If keyword is not supplied, then name is used to indicate both the keyword and the name of the parameter. If the keyword and name are given independently, the keyword is used when calling the method, and the name is used as the name of the parameter inside the body of the method.
The expression supplies a default value for the parameter.
It is used when the method is called and the keyword is not supplied. It is executed each
time the method is called and the corresponding keyword argument is not supplied. If
no expression is specified, the parameter corresponding to an unsupplied keyword
argument is initialized to #f
. The expression is
executed in a scope that includes all the preceding parameters, including required
parameters, the rest parameter (if any), the preceding keyword parameters, and the
next-method parameter (if any).
In the following example, all three keyword parameters have default values, and all three use the same name for the keyword and the parameter.
define method percolate (#key brand = #"maxwell-house", cups = 4, strength = #"strong") make-coffee (brand, cups, strength); end method;
The caller can choose which keyword arguments to supply and what order to supply them in:
percolate (brand: #"java", cups: 10); percolate (strength: #"strong", brand: #"starbucks", cups: 1);
The following method has two keyword parameters. In each, the name of the keyword and the name of the parameter is specified separately. The first keyword parameter has a default value, the second does not.
define method layout (widget, #key position: the-pos = 0, size: the-size) let the-sibling = sibling (widget); unless (the-pos = position (the-sibling)) align-objects (widget, the-sibling, the-pos, the-size); end method; layout(my-widget, position: 100, size: 500); layout(my-widget, size: query-user-for-size() );
The keyword parameter syntax in which the keyword name and
parameter name are given separately is needed to allow keyword names such
as position:
without forcing the method to use position
as a local
binding. If a method uses position
as a local binding, it cannot access the
module binding position
(which contains a function). The local binding would
shadow the module binding.
All required arguments must be supplied before any keyword arguments can be supplied. The
following call to layout
will signal an error:
layout(position: 100, size: 500);
Types for Keyword Parameters
When a type is indicated for a keyword parameter in a method, it is the same as establishing a type for a local binding. Specifically, the types of any keyword parameters are not used for method dispatch. Keyword parameter types are not allowed in generic function definitions, and do not figure into parameter list congruency.
The following two method definitions are equivalent:
method (#key X :: <integer>) ... X ... end method; method (#key X) let X :: <integer> = X; ... X ... end method;
If a keyword parameter is given a type, if #f
is not an instance of that type,
and if the keyword parameter is not given a default value, then the keyword parameter is
essentially required. An error of type <type-error>
will be signaled if a
call to the method does not include the keyword.
The following examples include keyword parameters that include both a type and a default value.
define method find-happiness (#key hint :: <symbol> = #"here") ... end method find-happiness; define method find-food (#key hint :: <restaurant> = lookup-default-restaurant()) ... end method find-food;
Return Value Declarations
Parameter lists may include value declarations. Value declarations come at the end of the
parameter list and are separated from the parameters by =>
. For each return
value, a value declaration can specify a name and an operand or just a name if the type
is <object>
. The complete syntax of value declarations is given
in Methods
on page 426.
The result of executing the operand at the time the function is defined is a type, called a value type. The name never comes into scope. It is included for documentation and for syntactic consistency with parameters. It is valid for the same name to be used in both one parameter and one value declaration in the same parameter list; this is useful as documentation that a function returns one of its arguments.
The last value declaration can be preceded by #rest
to indicate a variable
number of return values. A value declaration preceded by #rest
is called
a rest value declaration. A value declaration not preceded
by #rest
is called a required value
declaration. The value type in a rest value declaration is the type of each one of the
remaining individual values, not the type of a conceptual sequence of multiple values.
If a parameter-list does not contain a value declaration, it defaults to
=> #rest x :: <object>
. That is, the function can return any number of
values of any type.
A function will always return the number and types of values declared in its parameter-list. More precisely:
- Each value returned by a function must be an instance of the corresponding value type,
or else an error of type
<type-error>
will be signaled. - If fewer values are returned by the function's body (or by
the applicable method if the function is a generic function) than the number of required
value declarations in the function's parameter-list, the missing values are defaulted
to
#f
and returned. If#f
is not an instance of the corresponding value type, an error of type<type-error>
is signaled. - If a function does not have a rest value declaration, and more values are returned by the function's body (or by the applicable method if the function is a generic function) than the number of required value declarations in the function's parameter-list, the extra values are discarded and not returned.
Because of the parameter list congruency rules for value declarations
(see Parameter List Congruency
on page 93) the values
returned by a generic function do not have to be checked by the generic function. The check
inside a method will always be enough to verify that the return values are valid for the
generic function.
define method average (x :: <number>, y :: <number>) => mean :: <number>; (x + y) / 2 end method; // Returning multiple values define method limits (center :: <number>, radius :: <number>) => (min :: <number>, max :: <number>); values(center - radius, center + radius); end method; // The same name used both as a parameter and as a value type define method rotate (image :: <picture>, rotation-angle :: <number>) => (image :: <picture>); … end method; // This method can return one, two, or three values define method family (kid :: <person>) => (kid :: <person>, #rest parents); let mom = kid.mother; let dad = kid.father; case mom & dad => values(kid, mom, dad); mom => values(kid, mom); dad => values(kid, dad); otherwise => kid; end case end method family;
Note that the following example does not declare a return value of
type <number>
. It declares a return value of
type <object>
. To specify a type, both the name and the type must be
specified. If only one is given, it is taken as the name.
define method average (x :: <number>, y :: <number>) => <number>; truncate/((x + y), 2); end method;
Errata: In the published book,
the return value declaration is incorrect: => avg :: <number>;
Parameter List Congruency
For any given generic function, the generic function and all methods for that function must have congruent parameter lists. Two parameter lists are congruent if they satisfy the following conditions:
- They have the same number of required arguments.
- Each of the method's parameter specializers is a subtype of the corresponding parameter specializer of the generic function.
- One of the following is true:
- both accept keyword arguments
- both accept a variable number of arguments
- both require a fixed number of arguments
- If the generic function accepts keyword arguments, each method must recognize the mandatory keywords of the generic function.
In addition, the value declarations must be congruent, defined as follows:
- If the generic function's parameter list does not contain a rest value declaration,
then:
- The method's parameter list must not contain a rest value declaration.
- The two parameter lists must contain the same number of required value declarations.
- Each value type in the method's parameter list must be a subtype of the corresponding value type in the generic function's parameter list.
- If the generic function's parameter list contains a rest
value declaration, then:
- The method's parameter list is permitted, but not required, to contain a rest value declaration.
- The method's parameter list must contain at least as many required value declarations as the generic function's parameter list.
- Each value type in the method's parameter list must be a subtype of the corresponding value type in the generic function's parameter list. If the method has a rest value type, it corresponds to the generic function's rest value type. If the method has more required value types than the generic function, the extra ones must be subtypes of the generic function's rest value type.
Parameter Lists of Implicitly Defined Generic Functions
As a general principle, the parameter list of an implicitly defined generic function will impose as few constraints as possible on the methods that may be added. If a more constrained generic function definition is desired, an explicit definition should be used.
The parameter list of an implicitly defined generic function is determined by its method
definitions. These method definitions include both methods defined using define
method
and slot getter and setter methods defined using define
class
.
- The implicitly defined generic function has the same basic argument pattern as the methods. Either they must all require a fixed number of arguments, they must all accept a variable number of arguments, or they must all accept keyword arguments. A set of methods that includes members with more than one of these patterns violates the parameter list congruency requirement, and is an error.
- The implicitly defined generic function has the same number of required arguments as the methods. A set of methods that includes members with different numbers of required arguments violates the parameter list congruency requirement, and is an error.
- Each required argument of the implicitly defined generic function is specialized
on
<object>
. - If the implicitly defined generic function accepts keyword arguments, it does not have any mandatory keywords, nor does it accept all keyword arguments.
- The implicitly defined generic function has a rest value declaration
of
<object>
.
* In practice, an implementation may place a reasonable limit on the number of arguments that may be passed to any function.