I just committed changes that support supercalls in Avail. We had them for quite some time, but dropped them to make implementing guillemet expressions (repeated arguments) more tractable to implement. We also didn’t know how to support the combination of supercalls and guillemets simultaneously until fairly recently.
In a traditional object-oriented language, super calls are generally pretty
easy. Most language designers have not only single dispatch, but single
inheritance as well. Using the word “super” instead of “self” or “this” is
sufficient to direct the dispatch upward along its only possible path.
Avail is nothing like a traditional object-oriented language. We have multiple
supertypes. We may even have an infinite number of supertypes. We have to
take covariant and contravariant subcomponents into account when determining a
value’s type and its relationship to other types. We have repeated and grouped
arguments via guillemet groups. And finally, we dispatch on all arguments
symmetrically. So how the heck can we have super calls?
Let’s start with the syntax. Say we have multiple implementations of the “_+_”
operation, as a trivial example. It dispatches on both arguments
simultaneously, as in this example:
x : integer;
y : integer;
z ::= x + y;
The invocation of “_+_” uses arguments x and y to perform a dynamic lookup to
determine which method definition to actually invoke. It performs type
analysis statically to make sure the call to “_+_” is reasonable, but at
runtime it finds the most specific applicable implementation of “_+_” and
invokes it. It uses the specific values in x and y to do so, not just the
static types of the variables.
In essence, a super call in a traditional object oriented language is simply
providing additional lookup information so that it can find some implementation
other than the one which would be found by an ordinary regular message send.
Avail’s mechanism satisfies the same need for “routing” in a different way. In
order to specify a type that should be used during the lookup in place of the
actual argument value’s type, one cane supply a supercast phrase instead. It
has the form “(expression :: lookupType)”. The expression is the actual value
that will be passed as the argument, but the lookupType is an expression that
says what this argument should be treated as for the purpose of locating the
method definition to invoke. The lookupType must be a supertype of the
expression’s type (the type of value that the expression will produce). So we
can invoke “_+_” like this:
z ::= (x :: integer) + (y :: number);
This notation says to send the “_+_” message, but to find the most specific
implementation for arguments of type <integer, number>, as though the arguments
being passed were of those exact types. We can also try:
z ::= (x :: any) + (y :: any);
but it won’t typecheck, since the most general “_+_” operation is defined on
<number, number>.
But as with many of Avail’s features, it goes far beyond this ability to merely
statically pick out a specific method definition to invoke. Avail allows a
supercall to have only some of the arguments directed to specific types. For
example,
z ::= x + (y :: integer);
This says invoke the most specific applicable implementation of “_+_” that can
accept the actual value from the variable x at runtime for the first argument,
and the type integer for the second argument. Likewise we can have
z ::= (x :: integer) + y;
Which looks it up using integer for the first argument type, and the runtime
type of the actual value y for the second argument.
Avail can have any number of arguments, so the choice of supercasting is
independent for each argument of a call. Now consider the set construction
operation, “{«_‡,»}” (i.e., brace brackets around a list of expressions
separated by commas). The guillemets are what allow the repetition, and the
part after the double-dagger (‡) indicates what comes between the repeated
values, in this case a comma. The Avail parser arranges to have a tuple of
values passed as a single argument to this set constructor. So can Avail
express supercasts in a call to the set constructor? Yes. Here’s the
non-super call:
s ::= {1, 2, 3};
And here’s an invocation with a supercast:
s ::= {(1 :: integer), 2, 3};
This says to lookup the definition of the method “{«_‡,»)” using the tuple type
<integer, 2’s type, 3’s type…|3> for the sole argument. We likewise could have
used a supercast on each of the bottom-tier argument slots (essentially the
underscores, as distinguished from the list of arguments that the method body
accepts):
s ::= {(1 :: integer), (2 :: any), (3 :: number)};
In this case, the lookup is performed for a single argument of type <integer,
any, number…|3>. However, note that the actual tuple <1, 2, 3> is what gets
assembled and passed into the method body that was looked up.
We have several places in the Avail library that currently use reflection to
(statically) look up implementations of methods for particular argument types.
We then extract the body, which is just an ordinary function, and invoke it
with some arguments in a most unnatural way. What we really wanted was a
supercall in those places. I’ll be hunting down those dirty kludges in the
near future and replacing them with shiny new supercalls.
[I haven’t decided yet if I’m going to rework this into a blog post, but don’t
be surprised if similar material shows up in a different medium.]
signature.asc
Description: Message signed with OpenPGP using GPGMail
