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