#lang scribble/doc @(require scribble/manual (for-label scheme "../hierarchy.ss" "../multimethod.ss")) @title[#:tag "multimethod" #:style '(toc)]{Multimethods} @defmodule[(planet "multimethod.ss" ("murphy" "multimethod.plt" 1 0))]{ This module provides procedures and syntactic sugar to create multimethods and to attach procedures to them. Each multimethod is a structure that can act as a procedure accepting any number of arguments and any keyword arguments. When called, it first applies a signature generator to its arguments and keyword arguments, which should produce a single result. This generated signature is then compared with the signatures for registered method implementations to find the one to call. See @scheme[find-method] for a precise explanation of how methods are looked up. } @section{Procedural Protocol} @defproc[(make-multimethod [make-signature procedure?] [#:default default-signature any/c #f] [#:hierarchy ref-hierarchy (-> hierarchy?) global-hierarchy]) multimethod?]{ Creates a new multimethod. @scheme[make-signature] is the signature generator procedure applied every time the multimethod is called. @scheme[default-signature] is a default signature that is looked up in the method tables if the generated one doesn't match any implementation. @scheme[ref-hierarchy] is a procedure yielding the value hierarchy to be used by signature comparisons for the new multimethod. @scheme[ref-hierarchy] defaults to @scheme[global-hierarchy], ie. the default behaviour is to read the current value of the global hierarchy parameter every time the hierarchy is needed. } @defproc[(multimethod? [v any/c]) boolean?]{ Checks whether the given object is a multimethod. } @defproc[(multimehtod-make-signature [m multimethod?]) procedure?]{ Retrieves the signature generator of a multimethod. } @defproc[(multimethod-default-signature [m multimethod?]) any/c]{ Retrieves the default signature of a multimethod. } @defproc[(set-method [m multimethod?] [signature any/c] [method procedure?]) multimethod?]{ Takes the multimethod @scheme[m] and transforms it into a new multimethod that maps the given @scheme[signature] to the method implementation @scheme[method]. Any existing mapping for the given @scheme[signature] is replaced. } @defproc[(remove-method [m multimethod?] [signature any/c]) multimethod?]{ Takes the multimethod @scheme[m] and transforms it into a new multimethod that has no direct mapping for the given @scheme[signature] to any method implementation. } @defproc[(prefer-method [m multimethod?] [signature-a any/c] [signature-b any/c]) multimethod?]{ Takes the multimethod @scheme[m] and transforms it into a new multimethod that prefers dispatching via @scheme[signature-a] to dispatching via @scheme[signature-b]. } @defproc[(unprefer-method [m multimethod?] [signature-a any/c] [signature-b any/c]) multimethod?]{ Takes the multimethod @scheme[m] and transforms it into a new multimethod that does not prefer dispatching via @scheme[signature-a] to dispatching via @scheme[signature-b]. } @defstruct[(exn:fail:multimethod exn:fail) ([multimethod multimethod?] [signature any/c])]{ An exception raised to signal a problems with the given @scheme[multimethod], specifically concerning the given @scheme[signature]. } @defproc[(find-method [m multimethod?] [signature any/c]) procedure?]{ Retrieves the method implementation for multimethod @scheme[m] that matches the given @scheme[signature] best. The method lookup works as follows: @itemize{ @item{ The current hierarchy used by the multimethod is retrieved using the getter procedure passed to @scheme[make-multimethod]. If this value changed since the last time the multimethod was called, the new value is stored and the dispatching cache of the multimethod is cleared, ie. it is reset to the same state in which it was created. } @item{ The dispatching cache is consulted to find a method for the signature. If one is found, the search terminates. The result of subsequent search steps is later stored in the cache. } @item{ The method table is consulted to find a method directly associated with the signature. If one is found, the search terminates here. } @item{ A list of possible method implementations is built: @itemize{ @item{ All ancestors of the signature registered in the hierarchy used by the multimethod are retrieved. Then any methods associated to those ancestor signatures are fetched from the method table. } @item{ If the signature satisfies @scheme[class?], @scheme[interface?] or @scheme[dict?], the whole method table is scanned for mappings from candidate signatures that are ancestors of the reference signature as determined by @scheme[derived?]. } } All method implementations found in this way are sorted by their signatures. A signature sorts before another if one of the following conditions is met: @itemize{ @item{ The first signature is @scheme[derived?] from the second one. } @item{ The first signature is registered as preferred to the second one. } @item{ There is a registered preference and the first signature is @scheme[derived?] from the preferred signature while the less preferred signature is @scheme[derived?] from the second signature. } } If the resulting sorted list has exactly one entry, or if the sorting order between the first and second entries in the list is strict, the first entry of the list is used as the method implementation to call and the search terminates here. If the list has multiple entries but the first two are not strictly sorted, an error is raised indicating ambiguous methods. You can resolve this situation by defining more specific methods or by registering preferences. } @item{ If no method implementation was found so far, the search is retried from using not the original signature, but the multimethod's default signature. } @item{ If all steps up to now have failed to find a method. An error is raised indicating a lack of applicable method implementations. } } } @section{Wrapper Protocol} @defproc[(make-pmultimethod [m multimethod?]) pmultimethod?]{ Takes the multimethod @scheme[m] and wraps it in an object that behaves identically when it is called as a procedure. The new object stores the multimethod in a parameter. This way you can have the advantages of parameterization, which can be handy for dynamically adding or removing methods or preferences, without syntactic differences to regular procedures or multimethods stored in normal variables. } @defproc[(pmultimethod? [v any/c]) boolean?]{ Checks whether the given object is a parameter multimethod wrapper. } @defproc[(pmultimethod-parameter [pm pmultimethod?]) (parameter/c multimethod?)]{ Extracts the parameter object holding the multimethod from a wrapper. } @defproc[(pmultimethod->multimethod [pm pmultimethod?]) multimethod?]{ Extracts the current value of the parameter object holding the multimethod in a wrapper. } @section{Syntactic Sugar} @defform/subs[#:literals (::) (define-multi name+args :: signature options ...) ([name+args (name . args) name])]{ Expands to a @scheme[define] form assigning a parameter wrapped multimethod to @scheme[name]. If @scheme[args] are given, @scheme[signature] is implicitly wrapped in a lambda expression with @scheme[args] as arguments. Otherwise @scheme[signature] must evaluate to a procedure. In either case the resulting procedure becomes the signature generator of the new multimethod. The @scheme[options] are passed on to @scheme[make-multimethod] and may be used to specify keyword arguments. } @defform[(parameterize-multi (name ...) expr ...) #:contracts ([name pmultimethod?])]{ Extracts the parameter objects from the multimethod wrappers given by @scheme[name ...] and parameterizes them to their current values for the dynamic extent of @scheme[expr ...]. This is useful to install methods or preferences temporarily. } @defform*/subs[#:literals (::) [(define-method name+args #:default method ...) (define-method name+args :: signature method ...)] ([name+args (name . args) name]) #:contracts ([name pmultimethod?])]{ Attaches a new method implementation to the parameter wrapped multimethod @scheme[name] and stores the resulting new multimethod back into the parameter. The signature for the new method is either the default signature of the multimethod, if @scheme[#:default] is specified, or the given @scheme[signature]. @scheme[signature] is implicitly quasiquoted. If @scheme[args] are given, the method body specified by @scheme[method ...] is implicitly wrapped in a lambda expression with @scheme[args] as arguments. Otherwise @scheme[method] must be a single expression evaluating to a procedure. In either case the resulting procedure becomes the method implementation. }