I think we're beginning to converge; in many ways there's not a ton of
difference between the "categories" proposal and typeclasses.
>> What would you do with multiple instance declarations that differ by
>> specificity? For example, one declared on [int] and one declared on [T].
>
> Eventually, we'll have to specify a scoring scheme. The closest import
> always wins, so you can disambiguate explicitly. If you have multiple
> interface implementations imported at the same level, I'd make that an
> error in the first version of this system, but I agree that eventually
> we'll want specificity rules.
One of the things I do like about the categories proposal is that instance
declarations are named and scoped. This makes it possible to have finer-grained
control over what typeclass implementations are in scope, and it also makes it
possible to disambiguate explicitly when the system can't automatically resolve
ambiguities. Consequently it takes the pressure off us to try to construct the
"perfect" set of disambiguation rules.
Now from your proposal, I *love* the idea of unifying interfaces and
typeclasses (ie type parameter bounds), and I like the idea of separating
interfaces from implementations. (<bikeshed>I also, incidentally, like the name
`impl` that you suggested. I also have suggested `view` but Patrick really
doesn't like that. But I'm cool with `impl` much more than `category` or
`cat`.</bikeshed>)
So I would favor this combination of the two ideas:
- typeclasses are declared with `iface`
- typeclass instances are declared with `impl` and are named and scoped
- we have some overload resolution rules based on scope and specificity
- we allow explicit disambiguation by naming, e.g.: `x.I::y(...)` where I is an
impl
- we are conservative about avoiding subtle automatic resolution, and instead
rely on explicit disambiguation
>> This comes down to the different implementation approaches polymorphism.
>> Monomorphizing is rare in the ML/Haskell tradition, so type classes with
>> dictionary-passing is a nice fit. But it's not as clear what happens to type
>> classes when you introduce specialization.
>
> Monomorphizing generics that take interfaces sounds wrong. It is
> probably a win in some situations, and heuristics can be found to
> detect such situations, but I doubt we'll want to do in general.
>
> (What happens seems quite clear though -- you compile a version of the
> function with the vtable known at compile-time, so all dispatch
> becomes static.)
After thinking about this some more, let me try to make the case that
monomorphizing bounded generics is the right thing to do.
You mentioned the difference between
fn draw_all<T: drawable>(shapes: [T]) { ... }
and
fn draw_all(shapes: [drawable]) { ... }
In Haskell, this distinction is subtle, as it involves higher-rank types
(polymorphic types where at least one "forall" is not at the top level of the
type). That is, the second function is actually something like:
fn draw_all(shapes: [forall <T: drawable>. T]) { ... }
But as frightening as that makes it appear, it's a very common thing to want to
express in OO programming.
Now, we (rightly) haven't planned on allowing first-class polymorphism (aka
higher-rank types). Instead, we can use the more user-friendly OO idioms of
interfaces and classes for dynamic polymorphism.
So this leads to a clean separation of use cases. If you want static
resolution, you use bounded type parameters. If you want dynamic resolution,
you use interface types. With full monomorphization, you always get static
resolution for bounded type parmaeters. And instead of the mind-bending
forall-types, interface types are just the familiar dynamic pattern from OO
programming.
I think this combines the well-studied type discipline of Haskell with the
tried-and-true performance model of C++: with type parameters, you get static
resolution, and with objects (interfaces), you get vtables and dynamic
resolution.
>> For example, how do we prevent programmers from declaring some things to be
>> instances of `send` that aren't supposed to be?
>
> For `copy`, this is easy -- your `copy` method does a copy, and the
> compiler has the full type of the arguments when compiling the method
> body, so it checks, and complains when you implement a copy method on
> an invalid type. We'll have to introduce some explicit way of checking
> whether a type is sendable to do the same for `send`.
I guess what I was suggesting was that we currently have a notion of types that
*must not* be instances of `send`, and there's no way with typeclasses to
enforce *non*-implementation of an interface. But I'm beginning to suspect that
that's not a big deal. If you want to declare that a type implements `send`,
even when that type really shouldn't be, you can go ahead and do it, but the
implementation is going to be silly. For example, you could declare that @int
is sendable with a no-op implementation. It's a stupid thing for a programmer
to do, but it's not unsafe.
I think that's totally worth living with, for the benefit of having `send` just
be an iface. That is such a win in terms of mental burden on the user.
> We'll probably have to wire in automatic 'derived'
> instances/categories for this to be pleasant though, or you'll be
> forced to implement your own instances for every tag you declare. Or
> maybe `copy` and `send` will just look and behave like interfaces on
> the surface, and in fact refer to some built-in magic interface that
> the compiler handles specially.
Yeah, Haskell's `deriving` is nice.
>> And IINM, there are still blockers that are well-established and
>> fundamental, like stack growth and x64, no?
>
> Right, but those have very competent people working on them already
> (and are progressing really well).
>
> Actually, I agree that this isn't something that has any bearing on a
> first release. Coming up with new neat requirements at the last moment
> is a sure way to make that release be delayed. But at least having a
> proposal to point people to when they ask about support for dynamic
> dispatch would be a plus.
>
> (And yes, this will end up on the wiki, but I find that initial
> discussion is better done in the mailing list.)
Fair enough.
Dave
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev