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