RFP for a High-Level-Language

Kyle Hayes kyle@putput.cs.wwu.edu
Sat, 26 Nov 1994 11:26:01 +0800

I found the following note by Desmond Dsouza on comp.lang.dylan and thought
that he had some good points to consider about HLLs.


>From: dsouza@crl9.crl.com (Desmond Dsouza)

A point of view (disagreement?) with co-variance, contra-variance,
single-dispatch, multi-methods, encapsulation, and modules:

It is a suggestion that multi-methods and encapsulation are NOT
contradictory, and that there may be a way to bridge all 6 of 
the hot-buttons listed above.

This is not an attempt to fan yet another argument about whether
multimethods are good or bad with respect to encapsulation and OO,
so don't flame on that basis.


I've used Clos, C++, ST, and Dylan, and know exactly what I like:
  - I like multi-methods as a way of separating many different
    variants of "collaborative" behaviors (Clos, Dylan)
  - I like the single-object dispatch encapsulation for its notion of
    "who" is "responsible" for certain design knowledge (C++, ST). 
    It provides a nice strong focus for some design decisions and 
    allows me to design in terms of 'contracts' at the class level.
  - I like the notions of type and sub-type, but don't like
    blind application of contra-variant parameters for 'substitutivity'.
    I think multi-object dispatch solves that. 
    I also disagree with the claim some make that co-variance should 
    be used without some run-time dispatch or checking on the argument 
  - I like the higher-level modularization of Lisp Packages
    and Dylan modules. I would use modules in any of the above 3 cases, 
    and would really like multiple named module interfaces.

What are multi-methods? Multi-methods are implementations of 
some collaborative behavior which depend upon the type/class 
(ignoring the distinction for now) of several participating objects,
as opposed to just a single distinguished "receiver" object. We 
*separately* define the collaborative behavior for each interesting
combination of participant object types, and run-time dispatch
selects between them based on the combined types of all participants.

 foo (A1,B1,C2) 
     { .... collab. behavior for A1,B1,C2s and their descendants...}
 foo (A5,B3,C4) 
     { .... collab. behavior for A5,B3,C4s and their descendants...}

How difficult is it it to combine encapsulation with multi-methods as

In fact, I am hoping that Dylan's soon-to-be-announced macro facility
will allow me to fake at least some of this anyway. (I know that Craig
Chambers is trying to do some of this in the language Cecil, but think
his approach to encapsulation is very different from the one below).

Here is an example in pseudo-code. Note that I have described each variant
of the "multi-method" as "encapsulated" in a class, as well as having 
separately defined variants. Thus, you may read some of these as:
  "objects of class X can handle operation foo WITH args (Y,Z) or (P,Q)"

            class A
              m1 (int)
              m2 (int)

 class A1 : A       class A2 : A    // 2 subclasses of A
    m1 (int)             m1 (int)
    m2 (int)             m2 (int)
 end                 end
 class B
    // B's "know" how to handle operation "foo" with A1's or A2's
    // it is described in 2 methods (a-la multi-methods)
    foo (A1, int)   // B::foo (A1,int)
    bar (A2)        // B::bar (A2)
    foo (A2, int)   // B::foo (A2, int)
 class C
    // C's know how to handle "foo" with any A's
    foo (A,int)     // C::foo (A,int)

Pseudo-code syntax: receiver . message (arguments...)
In terms of dispatching, it would work as follows:
  a1 = A1 . new  // A1 handles "new" with no params
  a2 = A2 . new  // A2 handles "new" with no params
  b =  B . new
  c =  C . new

  // ok, lets exercise them
  b . foo (a1,25)    // calls B::foo(A1,int)
  b . foo (a2,25)    // calls B::foo(A2,int)
  c . foo (a1,25)    // calls C::foo(A,int)
  c . foo (a2,25)    // calls C::foo(A,int)
  c . foo (b,25)     // error (may be even caught at "compile time")
  self . private(...) // self call within some other method

For example, given types NightClub, Man, and Woman: NightClub may have
2 different implementations of "admit(Person)" depending on whether it
is a Man or a Woman. This discriminating behavior belongs in a
nightclub, although single-dispatch would suggest working out some
artificial protocol (e.g. double dispatch) to split the behavior
between Man, Woman, and NightClub.

The language Dylan has multi-methods, but does not have a
distinguished receiver for operations. It does have a notion of
"module" which is a namespace with controlled exports of names,
multi-methods, etc. As I understand it, when you invoke a generic
function, the set of multi-methods potentially invoked is decided by
module visibility rules. Hence, there may be methods which _could_
have served your request which will never be called because they are
not visible according to module rules.

It now seems that both strictly single-argument dispatch and
"generic" function dispatch can be viewed as special cases of this
'encapsulated multi-method':
  - Specifically, Dylan's multi-methods have the MODULE as the
    receiver!  i.e. the scope of multi-methods from which to 
    select is decided by module visibility, usually statically,
    and the specific method is chosen dynamically from this set.
  - ST/C++ dispatch has a 'normal' object as the receiver, but
    does not allow the "receiver" to separately define ITS
    collaborative behavior based on different collaborators: instead,
    all such collaborative behaviors must be stuffed into one method
    in the receiver. Discrimination must then be faked with 
    run-time type queries, double-dispatch, etc. Note that these are
    usually just mechanics of dynamic dispatch, manually coded, which
    are looking for "the applicable behavior".

I believe these issues (and more) have been addressed by Doug Lea 
in the book "OO System Development" (Addison Wesley, de Champeaux,
Lea, Faure). 

This approach also has a big effect on the long-standing
contra-variance and co-variance debate. The theoretical aspects of
contra-variance vs. co-variance, and substitutivity vs. overriding,
seem to have been recently addressed very nicely (by Giuseppe
Castagna, castagna@dmi.ens.fr).

So, what does this buy me?

1. A clear notion of objects of a class having certain "knowledge" and

2. Multi-methods, but scoped within the receiver's "knowledge".

3. The ability to now define multiple named class interfaces.
   Instances of a class play widely different roles, and each role 
   supports some protocols/interfaces.
   class B
        // B's "know" how to handle operation "foo" with A1's or A2's
        foo (A1, int)  // B::foo (A1,int)
        foo (A2, int)  // B::foo (A2, int)
        // B's "know" how to handle operation "foo" with C's
        foo (C, int)   // B::foo (C, int)

4. Client-Defined Polymorphism: a client's behavior can be polymorphic
   on the type of some participant object, _without_ having to visit
   and modify the code which defines that participant object. This is
   an extremely useful feature to have, and it could open the doors to 
   some very interesting definitions of multiple "views" of behavior.

5. The ability to use "modules" as a special case (at least for some
   aspects of how modules are used): there is only 1
   'instance' of each module. Any request not targeted at a specific
   receiver is 'caught', via some scoping rules, by the 'active' 
   module instance. Multiple named module interfaces is also a 
   special case.

6. Potentially, a way of clarifying when arguments types should be
   co-variant (for multi-dispatch?), and when they should be 
   contra-variant (for single-dispatch?). 

Comments? Violent disagreements?

Desmond D'Souza

ICON Computing, Inc.
12343 Hymeadow Drive, Suite 3C Austin, TX 78750
(v) 512.258.8437 (f) 512.258.0086 (e) info@iconcomp.com

12343 Hymeadow Drive, Suite 3C
Austin, TX 78750