As we've been going through the GC discussions, I've been thinking about
type classes vs. (Java/C++) interfaces. The harder I think about them, the
less semantic difference I see. I'm starting to wonder if a generalized
variant of C#-style instances isn't a functional *superset* of Haskell type
classes. The main difference seems to be that interfaces are types and type
classes aren't. Or more precisely, interfaces are *object* types while type
classes have only compile-time constant instantiations.

Before I dig in to this, let me say that instances as specifically
implemented in Java or C# obviously are *not* a functional superset of
Haskell or BitC type classes. But each time I run into something that looks
like a functional deficiency in Java-style interfaces, I seem to find an
easy way around it, so let me push on this a bit.
*
*
Certain things are clearly (I think) *not* differentiators between
interfaces and type classes:

The presence of self type *is* a differentiator, because an interface can
have non-static methods. Let's ignore that for a moment, and consider only
interfaces that consist exclusively of static methods. If Haskell had a
C#-style object type, that type could clearly be a type parameter to a TC
instance. Another way to express this is to say that an Instance with
non-static methods is instantiated over an encapsulated or existential
'this type parameter. Which is about what's happening, so that's not such a
bad intuition. This is the heart of why interface instantiation creates an
object. But as I say, stick with static-method-only interfaces for a moment.

The difference in operator overloading mechanisms change the way overload
resolution is performed, but I don't know that this changes anything
fundamental. Assume, for the sake of argument, that we remove from the
language the ability to directly overload operators with class methods or
interface methods, and instead use some sort of mixfix introduction syntax.
That will put both schemes on a common overload resolution framework. It
might not be a *good* framework, but the fact that it's common will
hopefully prevent us from getting distracted by the operator overloading
mechanisms.

Finally, let's take the type inference strategy out of the picture, because
we can use (or not use) similar inference strategies in both styles of
language.
*
*
I'm assuming a language in which first-class and/or anonymous procedures
are possible. Thus, I assume an interface notion in which an interface type
may include (non-virtual) procedure implementations. And I don't mean
"default" procedure implementations (though those could be done too).

I also assume that interfaces can be generic, in the sense that an
interface containing a procedure implementation can thereby introduce type
variables that are not resolved by the implementing class. The "missing"
type parameters are supplied at instance cast time, and cause the instance
procedures to be instantiated in a fashion very similar to TC instances. We
can even imagine partial specializations, though that isn't essential to
anything I'm going to say here.

Finally, I'm assuming that we can do what I'm going to call "Koenig
interfaces" (Andy'll be annoyed, but that'll give me a chance to see him
and Barb again, so definitely blame Andy for this feature if we implement
it ;-) ). That is: we can have a class C and an interface I that just
happen to be compatible, in the sense that *if* we had written "C
implements I" it all would have worked out. But we can go a small step
further by allowing something like:

C implements I by { method binding implementations }


Note that I am *not* assuming a model in which C is a subtype of I. I
suppose I'm assuming an implementation in which I is effectively a vTable
declaration, and "c as I" is really an object instantiation that pairs the
"this" pointer of c with a vTable pointer from I. So really, the cast from
C to I is really just an object instantiation for a wrapper object.

But once we view interface creation as object instantiation, it's really
not a big leap to imagine writing:

(B, C) implements I by { method binding implementations }


except that we need to name the respective instances of B and C and provide
a syntax for instantiation. Like, say (b, c) as I. Which kind of confirms
that *Interfaces are just proxy objects with a lot of convenience sugar for
one particular instantiation pattern*.

*Except* that an interface consisting exclusively of static methods need
not (indeed cannot) have an associated object. If SI (static interface) is
such an interface, then

f : SI -> int32


doesn't really take a formal parameter at all.


And the more I stare at this, the more I come to the conclusion that the "C
implements I by ... " thing is proof of a type relation (over the type
parameters of I) in exactly the same way that a type class instance is
proof of a type relation (over the type parameters of the TC).


There's even the same problem with the desire for multiple implementations.
We can imagine an interface Ord that we might want to implement as an
ascending or descending order.



So in the end, I'm left asking whether it may be true that the only
difference between TC's and Interfaces is that the lexical arrangement of
the respective languages have different scoping rules, and the
instantiation mechanism for interfaces guarantees a witness object so long
as there is at least one non-static member. It seems to be that the
"troublesome" cases are the "objectless" interfaces, which have all of the
same problems that TCs have (as well as the same resolutions).


Surely I'm missing something obvious here, and the relationship really
isn't this straightforward!




shap
_______________________________________________
bitc-dev mailing list
[email protected]
http://www.coyotos.org/mailman/listinfo/bitc-dev

Reply via email to