This section shortly touches upon important extensions to Function Object.
• Keyword Parameters. In addition to standard application, function objects may
provide keyword parameters. Accordingly, parameters can be passed in any 2Until today no EIFFELcompiler implements system-level validity checking, which is necessary to catch type errors resulting from mixing argument covariance with polymorphism.
order. This makes sense, in order to create useful abstractions. If the definition of the gravitational force in the example of section 7.8 on page 102 had been
GravityLaw m1m2r= G m1m2
r2 ,
(note the different order of parameters) we cannot define:
forcesurface=GravityLaw massearthradiusearth.3
With keyword parameters, notwithstanding, we can write:
force:=gravityLaw.m1(earthMass).r(earthRadius);
Keyword parameters effectively extend currying to partial application. Be- yond the reusability aspect above, however, they also enhance the clarity of programs: Consider the initialization of a node with a label and an identifica- tion. The code
node.make(a, b);
does not tell us what parameter plays which role. The meaning is hidden in the position of the arguments. Surely, we should use more expressive variable names but these should reflect the meaning in their environment, e.g.:
node.make(name, counter);
In other words, we use a nameand acounterto initialize a node but still are confused about the argument meanings. Only the version using keyword parameters —
node.label(name).id(counter);
— documents the meaning of arguments without compromising the docu- mentation of the environment variable meanings. In another case a nodeis possibly created using a number string and a random number which should be visible in the initialization statement. Keyword parameters respect that there is both an inner and an outer meaning for parameters and that both meanings are important to document.
• Default Parameters. A keyword parameter does not need to be mandatory.
Recipes may be designed to work for four persons by default but allow a optional adaption. So, both
cook.prepare(dinner);
and
3Functional programmers know this problem and use theflipfunction to exchange the order of (howbeit only) two arguments.
cook.prepare(dinner.people(2));
are valid. Default parameters in general have been described as the design pattern Convenience Methods [Hirschfeld96]. Unix shell commands are an example for the application of both default- and keyword parameters.
• Imperative result. It is possible to use the internal state of a function object to
calculate one or multiple results (addaccumulated resultstoConcreteFunction
in the structure diagram of section 7.5 on page 100). For instance, one func- tion object may count and simultaneously sum up the integers in a set during a single traversal. The set iteration client must request the results from the function object through an extended interface (add an arrow getResultfrom
aClienttoaFunctionin the diagram of section 7.7 on page 101). Note that im-
perative function objects may produce any result from a standard traversal algorithm. The latter does not need any adaption concerning type or whatso- ever.
• Procedure Object. If we allow function objects to have side effects on their
arguments we arrive at the Command pattern extended with parameters and result value. Procedure Object makes methods amenable to persistent command logging, command histories for undoing, network distribution of commands, etc. Like Command, Procedure Object may feature an undo
method, which uses information in the procedure object’s state to undo op- erations [Meyer88, Gamma et al.94]. Note how easy it is to compose a pro- cedure object, to be used as an iteration action, with a function object pred- icate that acts as a sentinel for the action. As a result, special iterations as “do if” [Meyer94b] can be replaced with a standard iteration.
• Multi-dispatch. Sometimes an operation depends on more than one argument
type. For instance, adding two numbers works differently for various pairs of integers, reals, and complex numbers. Simulating multi-dispatch with standard single-dispatch [Ingalls86] results in many additional methods (like
addInteger, addReal). The dispatching code is thus distributed over all in- volved classes. If, as in the above example, the operation must cope with a symmetric type relation (e.g.,real+int&int+real), each class has to know all other argument types.
A generic4 function object removes the dispatching code from the argument types and concentrates it in one place. It uses runtime type identification to select the correct code for a given combination of argument types. As such, it is not simply an overloaded Function Object, which would statically resolve the types.
Note that nested type switches can be avoided with partial parameterization: Upon receipt of an argument, a generic function object uses one type switch 4Named after CLOS’ [DeMichiel & Gabriel87] generic functions.
statement to create a corresponding new generic function object that will han- dle the rest of the arguments.
Unfortunately, the necessary switch statements on argument types are sensi- tive to the introduction of new types5. Yet, in the case of single-dispatch sim- ulation, new dispatching methods (e.g.,addComplex) are necessary as well. The goals of the Visitor pattern [Gamma et al.94] can be achieved with a com- bination of generic Function Object and any iteration mechanism. A generic function object chooses the appropriate code for each combination of opera- tion and element type. Once the generic Function Object has done the dis- patch, the exact element type is known and access to the full interface is pos- sible. Between invocations, function objects can hold intermediate results, e.g., variable environments for a type-checking algorithm on abstract syntax nodes.
A generic function object may even be realized as a type dispatcher, parame- terized with a set of function objects that actually perform an operation. This allows reuse of the dispatching part for various operations.
Visiting data structures with Function Object is acyclic w.r.t. data dependen- cies [Martin97] and does not force the visited classes to know about visi- tors [Nordberg96].
Finally, a generic function object is not restricted to dispatch on types only. It may also take the value of arguments into consideration. Consequently, one may represent musical notes and quarter notes by the same class. The corresponding objects will differ in a value, e.g., of attribute duration. Nev- ertheless, it is still possible to use a generic function to dispatch on this note representation.