Previous chapter: #20: New Syntax for Setter Variables (Change)

Dylan Design Notes: #21: Result Type Declarations (Addition)

Dylan Design Notes

#21: Result Type Declarations (Addition)

Version 1, March 1993

This design note introduces a way to declare the result types of methods and generic functions. This addition is intended to make code more self-documenting and allow for better compiler optimization. Type declarations will be checked at run time unless they can be proven at compile time to be satisfied always.


Parameter lists are extended to include value declarations, which must come at the end of the parameter list and are separated from the parameters by #values. Value declarations are of the form {variable-name | (variable-name type-expression)}.

At the time a function is defined, each value declaration's type-expression is evaluated to produce a value type. If type-expression is not specified for a particular value declaration, the value type defaults to <object>. When the function is invoked, each value returned must be an instance of the corresponding value type, or else an error of type <type-error> will be signaled.

? (define-method plus (x y #values (total <integer>)) (+ x y))
? (plus 22 3)
25
? (plus 1.5 7)
;type-error
The variable-name never comes into scope; it exists only for documentation and for syntactic consistency with parameters. It is valid for the same variable name to be used in both one parameter and one value declaration in the same parameter list.
(define-method always (object #values function)
   (method (#rest ignored 
            #values (object (object-type object)))
      object))
The last value declaration can be preceded by #rest to indicate a variable number of values returned. A rest value declaration is a value declaration preceded by #rest. A required value declaration is a value declaration not preceded by #rest.

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> will be 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.

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.

(define numbers (method ((start <integer>) (count <integer>)
                         #values (end <integer>)
                                 #rest (number <integer>))
  (bind ((end (+ start count)))
    (apply values end (range from: start through: (- end 1))))))
If a parameter list does not contain #values, it defaults to #values #rest (x <object>), i.e. the function can return any number of values of any type.

Generic functions can have value declarations in the parameter list used in define-generic-function. The values returned by the generic function will be instances of the value types.

(define-generic-function floor/ ((numerator <real>) 
                                 (denominator <real>)
                                 #values (quotient <integer>)
                                         (remainder <real>)))

(define-method floor/ ((numerator <small-integer>)
                       (denominator <small-integer>)
                       #values  (quotient <integer>)                 
                                (remainder <small-integer>))
  ...)
Rather than adding run-time checking to the generic function dispatch, the parameter list congruency rules are augmented to require each method added to the generic function to have congruent value declarations, defined as follows. add-method, define-method, define-class, etc. signal an error if this requirement is violated.

If the generic function's parameter list does not contain a rest value declaration:

If the generic function's parameter list contains a rest value declaration: If a generic function is implicitly defined by define-method or define-class, its value declarations default to #values #rest (x <object>) which imposes no restrictions on its methods.


The two reasons for the change are to make more compiler optimization possible by giving the compiler more information about types, and to make code more readable, again by supplying more information about types.

We chose the syntax to be as consistent as possible with the rest of Dylan. We included variable names in the value declarations, rather than using type-expressions alone, because names are useful to reduce confusion when there is more than one value, and because it makes the syntax consistent between the "parameters" and "values" portions of a parameter-list. In any case these variable names have no effect on the runtime. We are considering changes to this syntax in the future.

The type in a value declaration is called a "value type" rather than a "value specializer" so no one thinks it somehow affects method dispatch.

The semantics are incontrovertible, except for the question of whether returning a different number of values than declared should do what is proposed, return exactly that many values, or signal an error. Returning the declared number of values, as proposed, potentially allows a more efficient implementation because the caller does not have to check the number of values and the callee does not have to return a value count.

The parameter list congruency rules ensure that the number and types of values returned by a method are valid for the generic function. A method can have stronger constraints on its values than the generic function as a whole, if desired, and can return additional values specific to that method if the generic function allows it with a rest value declaration (which is the default).

If a generic function is implicitly defined by define-method, define-class, or define-slot, it is not given any value declarations. A programmer who wants value declarations on a generic function can write an explicit define-generic-function. In any case it is important for all implicit definitions of generic functions to be consistent in this respect.

Because of the way the values declaration defaults, this proposal does not change the behavior of any program that could be written in Dylan before this feature was added.

Next chapter: #22: BNF for Infix Dylan (Change)