Chapter 6
Functions
Overview
All operations in Dylan are functions.
Functions accept zero or more arguments, and return zero or more values. The parameter list of the function describes the number and types of the arguments that the function accepts, and the number and types of the values it returns.
There are two kinds of functions, methods and generic functions. Both are invoked in the same way. The caller does not need to know whether the function it is calling is a method or a generic function.
A method is the basic unit of executable code. A method accepts a number of arguments, creates local bindings for them, executes an implicit body in the scope of these bindings, and then returns a number of values.
A generic function contains a number of methods. When a generic function is called, it compares the arguments it received with the parameter lists of the methods it contains. It selects the most appropriate method and invokes it on the arguments. This technique of method dispatch is the basic mechanism of polymorphism in Dylan.
All Dylan functions are objects, instances
of <function>. Generic functions are instances
of <generic-function> and methods are instances
of <method>.
Generic Functions
Generic functions can be created with define generic or by
calling make on the
class <generic-function>. They are most often created
with define generic.
Generic functions may also be created implicitly
by define method or by slot specifications in
class definitions.
A generic function definition includes a parameter list, which constrains the methods that can be added to the generic function; some aspects of the parameter list must be matched by any method added. In addition, a generic function parameter list may specify that all keyword arguments are permitted in a call to the generic function.
Parameter list congruency is described on page
93. The complete syntax of define generic is given
on page 376.
The following definition defines a generic function that accepts a single required argument. All methods added to this generic function must also accept a single required argument.
define generic double (thing)
The following definition defines a generic function that accepts two arguments of
type <number>. All methods added to the generic function must accept two
required arguments of type <number> or subtype
of <number>.
define generic average (n1 :: <number>, n2 :: <number>)
Generic functions created with define generic may be sealed or open. For
details of this option,
see Declaring
Characteristics of Generic Functions
on page 135.
Methods
Methods can be created with define
method, local, and method program
constituents. define method is used to define a method and add it to a
generic function in a module binding. local is used to create local bindings
that contain self-recursive and mutually recursive methods. method is used to
create and return methods for immediate application, for use as function arguments, or for
storage in a variable or other data structure. Methods are also created for slot getters
and setters when a class is created.
Methods cannot be created with make.
The parameters and return values of a method are described in
its parameter list. The specializers in the parameter list declare the types of the
arguments acceptable to the method. The method can be called only with arguments that match
the specializers of the parameters. A complete description of parameter lists is given
in Parameter Lists
on page 84.
When the method is invoked, it executes its implicit body. Statements in the implicit body are executed in order, in an environment that contains the parameters bound to the arguments.
Methods may be invoked directly (used as functions), or indirectly through the invocation of a generic function.
Methods in Generic Functions
define method creates a method and adds it to a generic
function in a module variable. If the module variable indicated is not already defined, it
is defined as with define generic. Thus, define method will create
a new generic function or extend an old one, as needed. Methods added to a generic function
must have parameter lists that are congruent with the generic function's parameter list.
The following method accepts a single argument of type <number>, and
returns the number doubled. The method will be added to the generic function in the module
binding double.
define method double (thing :: <number>) => another-thing :: <number>; thing + thing; end method;
define method allows the programmer to control aspects of the sealing of the
generic function to which the method is added. For more details,
see Abbreviations for Define Sealed
Domain
on page 138.
A generic function with no required parameters can contain a single method. Adding a new method has the effect of replacing the existing method.
The complete syntax of define method is given
on page 377.
Local Methods
local is used for creating methods in local bindings. A
single local declaration may create one or more such methods. These methods may
be self-recursive and they may be mutually recursive with other methods created by the
same local declaration.
local is similar to let in that it creates local bindings in the
current body. The parameters and the bodies of the methods are within the scope of the
bindings. In this way, the methods can refer to themselves and to other methods created by
the same local declaration.
The complete syntax of local is given
on page 391.
define method newtons-sqrt (x :: <number>)
local method sqrt1 (guess)
// note call to other local method
if (close-enough? (guess))
guess
else
sqrt1 (improve (guess)) // note self-recursive call
end if
end sqrt1,
method close-enough? (guess)
abs (guess * guess - x) < .0001
end close-enough?,
method improve (guess)
(guess + (x / guess)) / 2
end improve;
sqrt1 (1)
end method newtons-sqrt;
Bare Methods
Methods can also be created and used directly with
the method statement.
Methods created directly can be stored in module variables, passed as arguments to generic functions, stored in data structures, or immediately invoked.
The following example creates a method and stores it in the module variable square. It is appropriate to define a method in this way (rather than with define method) when the protocol of the function being defined does not require multiple methods.
define constant square = method (n :: <number>)
n * n;
end method;
It is sometimes useful to create a method inline and pass it directly to another function that accepts a method as an argument, as in the following example.
// sort accepts a test argument, which defaults to \<
sort(person-list,
test: method(person1, person2)
person1.age < person2.age
end method)
Methods created directly with the method statement may be called directly or
they may be added to generic functions. Usually, however, when you want to add a method to a
generic function, you create and add the method in a single declarative step,
with define method.
Closures
Methods created with method or local can be passed to functions
and returned from functions. In both cases, the methods retain access to the lexical context
in which they were created. Such methods are called closures.
The following example defines a function that returns score-card
methods. The method that is returned is closed over
the score parameter. Each time this method is called, it updates
the score parameter and returns its new value.
define method make-score (score :: <number>)
method (increase :: <number>)
score := score + increase;
end method;
end method make-score;
define constant score-david = make-score(100);
define constant score-diane = make-score(400);
score-david(0)
⇒ 100
score-david(10)
⇒ 110
score-david(10)
⇒ 120
score-diane(10)
⇒ 410
score-david(0)
⇒ 120
Each invocation of make-score creates a new binding for score, so
each closure returned by make-score refers to a different binding. In this way,
assignments to the variable made by one closure do not affect the value of the variable
visible to other closures.
Errata: In the published book,
the score parameter is incorrectly written as points
in method make-score.
The following example defines a method for double that works on
functions. When you double a function, you get back a method that accepts arguments and
calls the function twice, passing the same arguments both times. The method that is returned
is closed over the function that was passed in as an argument.
define method double (internal-method :: <function>)
method (#rest args)
apply (internal-method, args);
apply (internal-method, args);
#f
end method
end method;
define constant double-david = double(score-david);
score-david(0)
⇒ 120
double-david(10)
⇒ 140
score-david(0)
⇒ 140