My proposal for polymorphic/generic/overloaded user functions in Joy

John Cowan — 2001-03-31 04:42:05

Right now, Joy primitive words are often polymorphic/generic/overloaded; for
example, "concat" applies equally to a pair of strings, sets, or lists.
However, although Joy *allows* the creation of user-level types, the
underlying type is almost always list, and so it is necessary to use a
Lisp/Scheme style of naming functions: a distinct name for each operation
on each type.

My proposal involves continuing to define such distinctly named functions,
but allowing them to be invoked by generic names. Thus, if we have
defined words foo-do, bar=do, and baz-do to "do something" to the user-level
types foo, bar, and baz, we can write a word "do" which will invoke foo-do,
bar-do, or baz-do based on whether the top stack item is a foo, a bar, or
a baz, and in such a way that "do" does not have to know about foo, bar, and
baz (and therefore continues to work even if zam-do is added to the system).

The mechanism for deciding which data are foos, bars, or bazzes is entirely
up to the programmer. Let us assume without loss of generality that
a foo is a list whose first member is the word foo, and likewise for
bars and bazzes. Then the predicate "type", defined as

DEFINE type == dup first unitlist.

will push the type of the top argument, wrapped in a unit list. Now we
are ready to introduce a novel combinator, which I will tentatively call
"dispatch".

"Dispatch" takes one quotation argument; however, the quotation is *not*
executed. Instead, the strings naming the words of which it consists
are concatenated in reverse order with "-" between them, and the symbol
table is searched for a defined function with that name. If it exists,
the stack is popped and it is executed. If no such definition exists,
the stack is unconsed and we try again. When the stack top is a null
list, "dispatch" signals an error.

Now we can define the generic/polymorphic function "do":

DEFINE do == type [do] swoncat dispatch.

What happens now when a user program invokes "do" with a foo on the stack?
When "dispatch" is executed, "[do foo]" is just above the foo object,
and the result is that the "foo-do" function is executed.

Suppose there is no "foo-do" function, and we want in that case to invoke
"baz-do" instead, because foos are a subtype of bazzes? In that case,
"do" is on the stack and the word "foo" is invoked. If its definition is

DEFINE foo == [baz] cons dispatch.

then "dispatch" will see a "[do baz]" on the stack and will execute baz-do.

This mechanism provides for simple OOP, but is obviously capable of great
flexibility. Any predicate whatever can stand in the place of "type".
Creating lists longer than two elements for "dispatch" to work on
allows the equivalent of inner types: "[do inner outer] dispatch" will
search in turn for outer-inner-do, outer-inner(with do on the stack),
and outer (with do and inner on the stack) functions. The generic
functions can check for several possibilities before dispatching,
and the type functions (like "foo" above) can do likewise, giving the
equivalent of multiple inheritance.

Comments?

--
John Cowan cowan@...
One art/there is/no less/no more/All things/to do/with sparks/galore
--Douglas Hofstadter