Usermode Multiple Dispatch in R and Common Lisp

The R solution to this is to make the function itself do the dispatch. A function like append would be defined as something which calls a different function name depending on the class of its argument: append.matrix for something of class matrix, append.numeric for something of class numeric, and so on. (Note that . in R is simply a normal character which can be used in identifier names.) I don’t know how well this would work for Ren-C, but it’s probably worth mentioning.

EDIT for clarification: the idea is that append.matrix etc. are completely normal functions which you can define yourself at any time. The generic method append simply dispatches to the relevant function based on its name. When defining a new class, you would also define whichever functions are relevant for use with that class.

(Also, for further inspiration, it may be worth looking at the Common Lisp Object System, though CL is less similar to Rebol than R is.)

1 Like

As a crude example of making a module's APPEND override the LIB version...

...the following overwrites the definition of APPEND seen by the module with a new one. It delegates to the LIB version if it doesn't find a class field in an object with an append member:

append: func [thing value] [
    either all [
        object? thing
        has thing 'class
        has thing.class 'append
    ][
         thing.class.append thing :value
    ][
         lib.append thing :value
    ]
]

So let's say you make an object matching the stylization:

obj: make object! [
    accumulator: 0
    class: make object! [
         append: func [thing value] [
             accumulator: accumulator + value
             print ["Accumulator is now:" accumulator]
         ]
    ]
]

Then you've "extended APPEND", sort of:

>> append [a b c] [d e]  ; delegates to LIB.APPEND
== [a b c d e]

>> append obj 10
Accumulator is now: 10

>> append obj 20
Accumulator is now: 30

Various Problems To Mention

  • Clearly this is brittle and slow, I just did it as an example.

  • You lose all the refinements on the spec of the generic this way. It could be done as an "enclose" that tunnels all the original arguments of the lib version, but then you have to worry about making sure you've not ignored any of the arguments when you don't decide to delegate to the native implementation.

    • R3-Alpha is notorious for having "actors" that implemented generic functions on PORT!s but not bothering to check any of the arguments.

    • I think a good solution to this problem has to be able to tell when you haven't heeded arguments passed to the generic.

  • This creates a secondary mechanism which is entirely unrelated to how LIB.APPEND branches off to different native code implementations for strings vs. blocks etc.

    • When I've thought of this problem in the past, I have imagined it being holistic... a single mechanism which covers both cases. e.g. the type system looking at a table of specs would be what drives it to select the right overload, sometimes toward a native implementation but sometimes toward a user one.

    • Given that the sort of "atomic" built in types are implementation-wise very different from objects perhaps such a unification isn't in the cards, at least not in the nearer future? I don't know, but it doesn't hurt to write suboptimal implementations out that sort of work as a talking point.

  • Here I went with the idea that APPEND doesn't just assume that because you've got a member function in your object named APPEND that is enough to mean it's part of the "overloading". I picked an arbitrary narrower convention.

    • It could be more specific, and use a mapping from the function value of archetypal append to the override...vs. the function name. For more readability and the same effect it could use a bound word, and heed the binding.

I looked at this a long time ago and have a vague recollection of thinking "no, not like that". But maybe doing a bad parallel implementation of it as an exercise would be useful as a talking point, like the above.

This is a bit different to what I described, because it’s storing the method in the object itself. A more R-like (and CLOS-like) solution would have some kind of global dispatch table where method implementations are stored, to which append delegates depending on its argument type.

Problem is, Ren-C at the moment has no sense of ‘class identity’ as is the case in R and CLOS. If you only have an object, there’s no way to tell which class it belongs to, and without that information it’s impossible to maintain any kind of dispatch table.

Your code instead reminds me of ‘prototype-based’ object systems without classes, as in JavaScript or io. Come to think of it, such a system may well be the most natural fit to Ren-C, although I couldn’t tell you precisely how the result would work.

(Incidentally, io is a lovely little language. I do recommend you have a look at it if you haven’t encountered it already.)