19.8 Typed generic functions
In addition to specifying the types of the parameters and return values of methods, you can specify the types of the parameters and return values of a generic function. You usually restrict the parameter types of a generic function to establish the contract of the generic function — that is, to define the domain of arguments that the generic function is intended to handle, and the domain of the values that it will return.
If we define a method without also defining a generic function, Dylan creates an implicit generic function with the most general types for each parameter and return value that are compatible with the method. For example, assume that we defined a method for next-landing-step, and did not explicitly create a generic function for it. The method is as follows:
define method next-landing-step
(storage :: <sky>, aircraft :: <aircraft>)
=> (next-class :: false-or(<class>), duration :: false-or(<time-offset>))
...
end if;
end method next-landing-step;
When we define a method without also defining a generic function, the compiler will generate an implicit generic function for us, which, in this case, will be as though we had defined the generic function like this:
define generic next-landing-step (o1 :: <object>, o2 :: <object>) => (#rest r :: <object>);
In Section 17.4, page 267, where we did define a generic function, we used a simple definition, just documenting the number of arguments, and giving them mnemonic names:
define generic next-landing-step (container, vehicle);
Because we did not specify types of the arguments or return values, they default to <object>, just as they did in the preceding implicit generic function.
Although the generic function that we wrote does prevent us from defining methods with the wrong number of arguments, it does not constrain the types of those arguments or the format or type of return values in any way. A sophisticated compiler may be able to make inferences based on the methods that we define, but we could both aid the compiler and more clearly document the protocol of next-landing-step by specifying the types of the parameters and return values in the definition of the generic function:
define generic next-landing-step
(storage :: <vehicle-storage>, aircraft :: <aircraft>)
=> (next-storage :: <vehicle-storage>, elapsed-time :: <time-offset>);
Now, the compiler can help us. If we define a method whose arguments are not a subclass of <vehicle-storage> and a subclass of <aircraft> (for example, if we provided the arguments in the wrong order), the compiler will report the error. Furthermore, the compiler can use the value declaration to detect errors in the return values (for example, if we returned only a single value or returned a value of the wrong type). Finally, the compiler can be asked to issue a warning if there is a subclass of the argument types for which no method is applicable.
In addition to establishing a contract, specifying the types of the parameters and return values of generic functions can allow the compiler to make additional inferences, as described in Section 19.3 with regard to truncate/. In the absence of other information, the compiler is limited in the optimizations that it can make based solely on the parameter types in the generic function, so it is generally best not to restrict artificially the types of a generic function, but rather to use the restricted types to document the generic function's protocol.




