Next: , Previous: , Up: syntaxes   [Contents][Index]


5.9 Overloaded functions

An overloaded function represents an aggregation of functions linked to the same name and having different number and/or type of arguments. For example, in the code:

(define/overload (fun {O <fixnum>})
  (list 'fixnum O))

(define/overload (fun {O <string>})
  (list 'string O))

(define/overload (fun {A <vector>} {B <vector>})
  (list 'vectors (vector-append A B)))

(fun 123)               ⇒ (fixnum 123)
(fun "ciao")            ⇒ (string "ciao")
(fun '#(1) '#(2))       ⇒ (vectors #(1 2))

an overloaded function named fun is defined by the first use of define/overload; subsequent uses add new specialisations to the same function.

In the above example: the selection of the specialised function happens at expand–time, because the expander can determine the type of the operands. Early binding (more precisely: expand–time dispatching) is available only with the “canonical” function application syntax:

(?fun ?operand ...)

Here is an example of late binding (more precisely: run–time dispatching, or dynamic dispatching):

(define/overload (doit {O <string>})
  (list 'string O))

(define/overload (doit {O <fixnum>})
  (list 'fixnum O))

(map doit '("ciao" 123))
⇒ ((string "ciao") (fixnum 123))

putting the overloaded function’s syntactic identifier in reference position will reference a special function that implements dynamic dispatching.

It is an error to define two specialised functions with the same formals type signature. Specialised functions are allowed to have different type signatures for the returned values, but we should use this feature with care; it is suggested not to abuse overloaded functions by adding specialised functions that perform unrelated operations.

We can overload functions only in the same lexical context, the following code will create two functions:

(define/overload (fun {O <fixnum>})
  ---)

(internal-body
  (define/overload (fun {O <string>})
    ---)
  ---)

We can define an overloaded function in a library and then add specialised functions in a second library which depends on the first one.

Whenever an overloaded function is applied to a tuple of operands: first expand–time early binding is attempted; if it fails, run–time late binding is attempted. At expand–time:

  1. The expander determines the type of the operands, if it fails a syntax violation is raised (for example if an operand expression returns multiple values).
  2. The set of specialised functions is visited and the syntactic identifier bound to the better matching one is inserted in the code to be applied to the operands. The return values of the specialised function will become the return values of the overloaded function application.
  3. If no matching function is found: code performing late binding is inserted. If the option -Woverloaded-function-late-binding was used: a &warning exception is raised signalling failure in early binding.

At run–time:

  1. The type of the operands is determined using type-descriptor-of, type-descriptor-of.
  2. The set of specialised functions is visited and the better matching one is applied to the operands. The return values of the specialised function become the return values of the overloaded function application.
  3. If no matching function is found: an exception is raised with type &overloaded-function-late-binding-error, (vicare-scheme)Overloaded function late binding conditions.

Specialised functions are ranked to select the better matching. At both expand–time and run–time, ranking works by iterating a list of specialised functions:

  1. Get the next function from the list. When there are no more functions: if a better matching function was selected, it becomes the one; if no matching function was found, perform the default action.
  2. If the formals’ type signature of the next function is a super–type of the operands’ type signature: the function is the “next matching one”, otherwise it is discarded and we iterate to step 1.
  3. If the next matching function is the first in the list: it becomes the “current better matching” and we iterate to step 1.
  4. If the formals’ type signature of the next matching is a sub–type of the formals’ type signature of the better matching: the next matching becomes the new better matching; otherwise the old better matching stands. We iterate to step 1.

Here is an example that shows how ranking works:

(define/overload (fun {O <number>})
  `(number ,O))

(define/overload (fun {O <real>})
  `(real ,O))

(define/overload (fun {O <fixnum>})
  `(fixnum ,O))

(fun 1+2i)      ⇒ (number 1+2i)
(fun 3.4)       ⇒ (real   3.4)
(fun 5)         ⇒ (fixnum 5)

notice that, at expand–time, only the operands’ type signature seen by the expander is the one that matters, with the same definitions above:

(fun (cast-signature (<number>) 123))   ⇒ (number 123)
(fun (cast-signature (<real>)   123))   ⇒ (real   123)
(fun (cast-signature (<fixnum>) 123))   ⇒ (fixnum 123)

The following syntactic bindings are exported by the library (vicare).

Syntax: define/overload (?who . ?formals) . ?body
Syntax: define/overload ((brace ?who . ?rv-types) . ?formals) . ?body
Auxiliary Syntax: brace

Define a specialisation for the overloaded function ?who. If this is the first specialisation for ?who, also define the overloaded function; otherwise define only the specialisation and register it in the already existent ?who.

The syntax has the same format of define/typed, it defines a function with type annotations for both the arguments and the return values. The function’s type signature is used only at expand–time to validate the type of the operands used in an application.


Next: , Previous: , Up: syntaxes   [Contents][Index]