In a previous entry (see Musings on the tagged language (part 3) (2015 February 15)) I discussed the idea of allowing function overloading in the tagged language as the way of defining type tag methods and field accessors; function overloading is clos–like multimethods with dispatch at expand–time. It consists of having a global table of multimethod names that is queried every time we need to expand a form like:
(?identifier ?arg ...)
the expander searches for the name of ?identifier in the table; so the following expansions can take place:
(length '(1 2)) → (list-length '(1 2)) (length '#(1 2)) → (vector-length '#(1 2)) (length '#vu8(1 2)) → (bytevector-length '#vu8(1 2)) (length "1 2") → (list-length "1 2")
because the expander searches for the string "length"
in the table
(searching for the symbol length
is more efficient but equivalent,
because symbols are interned by using their string name as key; so what matters here,
conceptually, is the string name).
In the previous entry (see Musings on the tagged language (part 3) (2015 February 15)) I concluded that the cleanest way to select a multimethod from the namespace of multimethods would be to introduce a specific built–in syntax:
(method-call length ?obj)
and then think of some reader syntax to save typing a lot of characters; for example, “quoting” with a colon:
:(length ?obj)
Is this actually good? The more I think of it, the more transparent multimethod call is preferable; even if it breaks normal identifier binding resolution.
clos’s multimethods have a number of features; some of them can be used with function overloading, too; other features cannot.
(nausicaa)
library provides not
only :primary
methods, but also :around
, :before
and
:after
methods. I still have to wrap my head around this concept to
understand its actual usefulness, but I do not see a reason overloaded functions
could not have them, too.
(nausicaa)
library provides
call-next-method
and next-method?
. call-next-method
can be
implemented for overloaded functions: no problem. What about the next-method?
predicate? There are at least three possibilities:
next-method?
is a syntax expanding to #t
or
#f
; the result of such expansion cannot be used at expand–time. We
could write:
(if (next-method?) (do-something) (do-something-else))
which would expand to one among:
(if #t (do-something) (do-something-else)) (if #f (do-something) (do-something-else))
and the compiler can optimise it to:
(do-something) (do-something-else)
maybe useful, maybe not.
next-method?
is a cond-expand
clause, which is
meant to be used at expand–time. We could write:
(cond-expand (next-method? (do-something)) (else (do-something-else)))
which would expand to one among:
(do-something) (do-something-else)
maybe useful, maybe not.
next-method?
is a function that can be used by macro
transformers. We could write:
(define-syntax (doit stx) (if (next-method?) #'(call-next-method) (syntax-case stx () ((_) #'(do-something-else)))))
if the macro doit
is used in the body of an overloaded function having a
next–method: (next-method?)
returns #t
, otherwise it returns #f
;
maybe useful, maybe not. This would be the most flexible: this implementation allows
us to do everything that can be done with the others.
eql
parameter specialisers; they are a run–time thing. This feature cannot be
implemented in overloaded functions.
Function overloading should, really really, not interfere with macro expansion. If we do:
(import (vicare)) (add-method length (<string>) string-length) (define (frob obj) ---) (let-syntax ((length (syntax-rules () ((_ ?expr) (frob ?expr)) ))) (length "ciao"))
or:
(import (vicare)) (add-method length (<string>) string-length) (define (frob obj) ---) (define-syntax length (syntax-rules () ((_ ?expr) (frob ?expr)) )) (length "ciao"))
the following expansion must take place:
(length "ciao") → (frob "ciao")
When processing the form:
(?identifier ?arg ...)
the expander must (in this order):
Vicare has standard local syntax definitions and fluid syntaxes (special
identifiers that can be rebound without breaking the result of
free-identifier=?
). It goes like this:
#!r6rs (import (vicare)) (define-fluid-syntax ciao (identifier-syntax "ciao")) (define-syntax hello (identifier-syntax "hello")) (define-syntax do-ciao (identifier-syntax ciao)) (define-syntax do-hello (identifier-syntax hello)) (fluid-let-syntax ((ciao (identifier-syntax "ohayo"))) (display do-ciao) (newline)) (let-syntax ((hello (identifier-syntax "ohayo"))) (display do-hello) (newline)) (flush-output-port (current-output-port)) -| ohayo -| hello
Similarly, it makes sense to allow local function overloading. It should go like this:
(import (vicare)) (add-method length (<string>) string-length) (length "ciao") → (string-length "ciao") (let-method ((length (<string>) frob)) (length "ciao")) → (frob "ciao")
What should happen if a name is present in the global table of multimethods, but no specialised method matches a given arguments signature? Example:
(import (vicare)) (define (frob obj) (display str) (newline)) (define (frob-string str) (display str) (newline)) (add-method frob (<string>) frob-string) (frob '(1 2))
there is no frob
method for <list>
, so:
frob
rather than the
multimethod frob
.
missing-method
to the
syntax object (syntax (frob '(1 2)))
? Such function could cause the expander
to move on with standard binding resolution, or do something else.
Maybe, maybe, maybe things are getting into shape. At least in my mind.