Methods
Previous sections discussed methods as components of generic functions. In Dylan, methods can also be created and used directly. Direct use of methods is appropriate in several situations:
Methods may be called directly or they may be added to generic functions. FN26
In general, when you want to add a method to a generic function, you create and add the method in a single step, with define-method. The method special form is most often used to create local methods that do not need to be added to a generic function. It can also be used to create a function that cannot be further specialized.
The body-forms are evaluated in order, in a scope in which each name is bound to a method specified by the corresponding parameter-list and method-forms. The values of the last body-form are returned.
The scope of the names also includes the parameter lists and the bodies of the methods. This means that the methods can refer to themselves and to the other methods created by the bind-methods form.
The basic tool for creating methods is the special form method. The special form bind-methods can be used for creating self- and mutually-recursive methods.
method[return to first citation] parameter-list form1 form2 ... ==> <method> [Special Form]
method creates and returns a method that accepts the arguments described by parameter-list and then executes the forms in order. The forms taken together are called the body of the method. The format of the parameter-list is described in the definition of define-method, given in Chapter 6.
? (method (num1 num2)
(+ num1 num2))
{an anonymous method}
;the second argument to SORT is the test function
? (sort person-list
(method (person1 person2)
(< (age person1)
(age person2))))
? (bind ((double (method (number)
(+ number number))))
(double (double 10)))
40
In the section on generic functions and methods, we defined the generic function double and gave it methods for numbers and sequences. 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 arguments both times.
? (define-method double ((my-method <function>))
(method (#rest args)
(apply my-method args)
(apply my-method args)
#f))
double
? (define print-twice (double print))
print-twice
? print-twice
{an anonymous method}
? (print-twice "The rain in Spain. . .")
The rain in Spain. . .The rain in Spain. . .
#f
? (print-twice 55)
5555
#f
bind-methods[return to first citation] (method-spec1 method-spec2 ...) [Special Form]
body-form1 body-form2 ...
==> values
A method-spec has the form (name parameter-list method-form1 method-form2 ...)
? (define-method root-mean-square ((s <sequence>))
(bind-methods ((average (numbers)
(/ (reduce1 + numbers)
(length numbers)))
(square (n) (* n n)))
(sqrt (average (map square s)))))
root-mean-square
? (root-mean-square '(5 6 6 7 4))
5.692099788303083
The next example calculates a square root using Newtons method of successive approximations. The function sqrt1 can refer to itself recursively as well as to the other bound methods, close? and improve.
? (define-method newtons-sqrt (x) (bind-methods ((sqrt1 (guess) (if (close? guess) guess (sqrt1 (improve guess)))) (close? (guess) (< (abs ( (* guess guess) x)) .0001)) (improve (guess) (/ (+ guess (/ x guess)) 2))) (sqrt1 1))) newtons-sqrt ? (newtons-sqrt 25) 5.000000000053723See Also Dylan Design Notes: Method Specificity (Change)
Method Dispatch
In Dylan, a generic function contains of a number of methods. When you call a generic function, the generic function uses the classes and identities of the arguments to determine which methods to call. This process is called method dispatch.
When you define a method, you specify the classes or identities of the arguments appropriate for the method. This is called specialize[return to first citation]ing the method.
When a generic function performs method dispatch, it chooses the most specific method to run. Imagine three methods defined for double: one method specializes on <number>, one specializes on <sequence>, and one specializes on <function>. When we call double with 5 as an argument, the choice of methods is easy. 5 is not a sequence or function, but it is a number. Therefore the method on number is run.
But what if we add another method to double, which specializes on <integer>?
Singleton specializers are considered more specific than any class.
In the previous example, the double method for <integer> completely overrode the double method for <number>. In many situations, however, a subclass wants to modify the behavior of a method, rather than replace it completely; it wants to perform some work but also use the inherited behavior. This can be accomplished with next-method. next-method is a function that, when called, invokes the next most specific method applicable in the generic function. The next-method is the value of the #next parameter. Normally this parameter is named next-method, though it can have other names at the programmers discretion.
In the following example, the double method for <float> prints out a notice and then calls next-method, which invokes the next most specific method, in this case the method for <number>. FN27
Passing Different Arguments to Next-Method
In the usual case, next-method is called with no arguments. This indicates that the next-method should be passed the same arguments that were supplied to the current method.
It is valid to supply arguments, including different arguments, when calling next-method. However, if you pass different arguments, the new arguments must result in the same ordered sequence of applicable methods, in the same order, as the original arguments. Otherwise, the behavior of Dylan is undefined.
In some cases, the methods in a generic function accept different keyword arguments. In such cases, its convenient for the methods also to accept a rest parameter. That way, all the non-required arguments to the generic function are captured in the rest parameter. By using apply, the next-method can be invoked with the complete set of arguments.
The Next-Method Parameter
The next-method parameter is passed behind the scenes. When a method is called by its generic function, the generic function dispatch mechanism automatically passes the appropriate value for next-method. There is no way for a user program to specify the next-method argument when calling a method.
If you create a method directly (i.e., with method rather than with define-method) and you want this method to accept a next-method parameter, then you should insert a #next into the parameter list explicitly. You would do this if you are creating a method that you plan to add to a generic function, and you want this method to be able to call next-method. You can also supply the next-method parameter when using define-method, in cases where you want to give the parameter a different name.
Multiple Argument Dispatch
In the simplest case, only one parameter of a method has a specializer. In this case, the dispatch happens completely on the one specialized argument. However, it is possible to specialize on more than one parameter. When more than one parameter is specialized, all the arguments must match all the specializers for the method to run. Methods that specialize on more than one parameter are called multimethods.
Multimethod dispatch occurs in three phases. First, all the applicable methods are selected, then these methods are sorted, then the most specific is called.
A method is considered applicable if all its specializers match the arguments of the call. If the specializer is a class, a match occurs when the argument is a general instance of the class. If the specializer is a singleton, a match occurs when the argument is the singletons object. The comparison of arguments to singleton objects is performed with id?.
The applicable methods are sorted according to their specializer lists, which are compared lexicographically using a function which considers a singleton less than any class and considers a class less than any of its superclasses. The result is a lexicographic sort, based on the specializer lists of the methods.
Multimethods are useful in Dylan to provide multiple levels of discrimination. For example, imagine the generic function show, which displays an object on an output device. We could define methods on show that dispatch first on the class of output device and next on the class of object being displayed.
The class <generic-function> supports the following init-keywords:
Generic functions are not usually created directly. Most often they are created by define-method.
In general, you do not need to call add-method directly. It is called by define-method.
If you add a method to a generic function, and the generic function already has a method with the exact same specializers, then the old method is replaced with the new one.
A single method may be added to any number of generic functions.
add-method returns two values. The first is the new method. The second will be either the method in generic-function which is being replaced by method, or it will be #f, if no method is being replaced.
The number of specializers given must match the number of required arguments to generic-function.
Given a generic function frob with two required arguments, the call (freeze-
methods frob <object> <object>) prevents the addition of any methods to frob. The call (freeze-methods frob <sequence> <object>) prevents the addition of any methods whose first specializer is <sequence>, a subclass of <sequence>, or a singleton on a general instance of <sequence>.
The methods in generic-function that are affected may be combined so that they appear as a single method to introspective functions such as generic-function-methods.
The following functions dont need to be called in routine programming, but are useful for implementation of the language and in the construction of the programming environment.
? (define-method double ((thing <number>))
(+ thing thing))
double
In this example, we are defining a method on double which is applicable when double is called with a general instance of <number>. Note that the instance can be a direct instance or an indirect instance of the specializer class. In the example, the argument can be an integer, a floating-point number, or any other kind of number.
? (double 10)
20
? (double 4.5)
9.0
Most Specific Method
? (define-method double ((thing <integer>))
(* thing 2))
double
If we call (double 5) now, which method is run: the method for <integer> or the method for <number>? 5 is both an integer and a number. The answer is that the more specific method is run. Because <integer> is a subclass of <number>, and because 5 is an instance of <integer>, the method for <integer> is run. This rule allows subclasses to override the methods of their superclasses.
? (define-method double ((thing (singleton 'cup)))
'pint)
double
? (double 'cup)
pint
See Also: Dylan Design Notes: Method Dispatch Ambiguity (Clarification)
Calling More General Methods
? (define-method double ((num <float>))
(print "doubling a floating-point number")
(next-method))
double
? (double 10.5)
doubling a floating-point number
21.0
If there are no more methods available, the next-method parameter will be bound to the value #f instead of to a method.(define-method show ((device <window>) (thing <character>))
...)
(define-method show ((device <window>) (thing <string>))
...)
(define-method show ((device <window>) (thing <rectangle>))
. . .)
(define-method show ((device <file>) (thing <character>))
. . .)
(define-method show ((device <file>) (thing <string>))
. . .)
Additional Function Details
<function> [Abstract Class]
<function> is the class of all objects that can be applied to arguments. It is the superclass of generic functions and methods. <function> inherits from <object>.
<generic-function> [Instantiable Class]
<generic-function> is the class of functions which contain methods. Generic functions inherit from <function>.
The new generic function initially has no methods. An error will be signaled if the generic function is called before methods are added to it. Once a generic function is created, you can give it behavior by adding methods to it with add-
method or define-method.
? (make <generic-function> required: 3)
{an anonymous generic function}
? (make <generic-function> required: 3
debug-name: 'foo)
{the generic function foo}
? (define expand
(make <generic-function> required: 1 debug-name: 'expand))
{the generic function expand}
? (expand 55)
error: no applicable method for 55 in {the generic function expand}
<method> [Abstract Class]
Methods inherit from <function>.
add-method generic-function method [Generic Function]
==> new-method old-method
add-method adds method to generic-function. This operation modifies the generic function object.
make-read-only[next citation] generic-function ==> generic-function [Generic Function]
make-read-only makes generic-function readonly. This means that methods can no longer be added to or removed from the generic function. The compiler can perform additional optimizations on read-only generic functions.
freeze-methods generic-function #rest specializers [Generic Function]
==> generic-function
freeze-methods prevents certain methods in generic function from being replaced, shadowed, or removed. Specifically, no methods with specializers that match or are more specific than specializers can be added to or removed from generic-function. (Because replacing a method involves removing the old one, no methods with matching or more specific specializers in a generic function can be replaced.)
generic-function-methods generic-function [Generic Function]
==> sequence
generic-function-methods returns a sequence of all of the methods in generic-function. The order of the methods in the sequence is not significant. FN28
The sequence returned should never be destructively modified. Doing so may cause unpredictable behavior.
method-specializers method ==> sequence [Generic Function]method-specializers returns a sequence of the specializers for method. The length of the sequence will equal the number of required arguments of method. The first element of the sequence will be the specializer of the first argument of the method, the second will be the specializer of the second argument, etc.
The sequence returned should never be destructively modified. Doing so may cause unpredictable behavior.
function-arguments function [Generic Function] ==> required-number rest-boolean kwd-sequencefunction-arguments returns three values:
applicable-method function #rest sample-arguments [Generic Function] ==> booleanapplicable-method? returns true if function is a method or contains a method that would be applicable for sample-arguments.
sorted-applicable-methods [Generic Function] function #rest sample-arguments ==> sequencesorted-applicable-methods returns a sequence of the methods in function that are applicable for the sample-arguments. The methods are sorted from most to least specific.
The sequence returned should never be destructively modified. Doing so may cause unpredictable behavior.
find-method generic-function specializer-list [Generic Function] ==> {method or #f}find-method returns the method in generic-function that has the specializers in specializer-list as its specializers. The specializers must match exactly for a method to be returned.
remove-method; generic-function method ==> method [Generic Function]remove-method removes method from generic-function and returns method. remove-method will signal an error if method is not in generic-function.