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.]

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to