So the question is now: how do we recover objects without having
subtypes?
Actually, this turns out to be separable into two questions:
1. How do we recover a suitably constrained form of existential
type (objects)?
2. How do we recover inheritance?
Objects without inheritance really doesn't seem to create any new
complication, and we don't plan to remove them -- we need them to get
out existential types.
Inheritance is trickier, and it's useful to ask why, exactly, we wanted
it.
There are two reasons for inheritance:
1. Implementation re-use: types A, B have a common supertype Super,
and we want to writesome function that accesses only the common
fields in Super.
2. Virtual methods, but these seem to be largely subsumed by
type classes.
The key observation here is that there are two ways to get
implementation re-use. The first is to avoid generating multiple
implementations, and the second is to notice that all of the
implementations are identical and use just one.
What we now have in mind is to introduce a new constraint:
(has-field 'a name 'b)
which is satisfied when the type 'a has a field with the given /name/,
and that field in turn has type 'b. So the type of:
(define (get-x v) v.x)
get-x (forall ((has-field 'a v 'b))
(fn 'a -> 'b))
that is: we will now do polymorphic inference over field names. On the
one hand, admittedly, this is pretty damned ugly, because those
HAS-FIELD constraints will accumulate very quickly. But it is no worse
than what we have today, because today structure types were not inferred
at all. For example, if we re-write that procedure as:
(defstruct mystruct x:int32)
(define (get-x v:mystruct) v.x)
get-x: (mystruct -> int32)
which is what we would get today. So this is a case where field types
will be inferred if unspecified, which is good for prototyping and
arguably bad for readability. But I guess my feeling here is that:
1. In most cases, you have a specific structure type in mind when you
write a procedure that accesses a structure. Otherwise the only
reason to write this sort of thing is to illustrate the full
potential ugliness of a full inference system. I feel comfortable
not trying to optimize the readability of perverse examples. :-)
2. In most other cases, it will turn out that you are trying to use
common sub-fields in single-inheritance style.
So what about single-inheritance and code re-use?
The proposal is that we can recover this with type classes. Basically,
we can have a simple empty type class EXTENDS and then do something
like:
(defstruct (S 'a) extends (Super 'a)
... fields ...)
(definstance (Extends (S 'a) (Super 'a)))
if this pattern is useful enough we can even automated it as part of the
behavior of defstruct. The key point here is that EXTENSION IS NOT
TREATED AS SUBTYPING.
Later, when we intend to use common leading fields and we want to tune
down the HAS-FIELD noise, we can write:
(forall ((EXTENDS 'Sub Super))
(define (f-shared x:(ref 'Sub))
... access only the fields of the superclass here...))
and what will happen is that because all of the field offsets and types
match we will only get a single instantiation of f-shared. We aren't
going to get that implementation re-use optimization in the very near
term, but we will do so eventually.
More on how to recover virtual methods coming, but I need to think that
through a little bit more.
_______________________________________________
bitc-dev mailing list
[email protected]
http://www.coyotos.org/mailman/listinfo/bitc-dev