On Sat, Nov 16, 2013 at 1:07 AM, Huon Wilson <dbau...@gmail.com> wrote:

> On 16/11/13 03:05, Gábor Lehel wrote:
>
>> It would be nice if `Trait1 + Trait2` were itself a trait, legal in the
>> same positions as any trait. This is already partly true: in trait bounds
>> on type parameters and super-traits of traits. Where it's not true is trait
>> objects, e.g. `~(ToStr + Send)`. Having this could remove the need for the
>> current `~Trait:OtherTraits` special syntax.
>>
>> I wonder whether lifetimes could also be interpreted as traits, with the
>> meaning: "[object of type implementing lifetime-trait] does not outlive
>> [the given lifetime]". This is an honest wondering: I'm not sure if it
>> makes sense. If it does make sense, it would fit in perfectly with the fact
>> that 'static is already a trait. Together with the above, it might also
>> allow a solution for capturing borrowed data in a trait object: you could
>> write `~(Trait + 'a)`.
>>
>>
> How does this interact with vtables?
>

Good question. Here's how I think it might work: `+` would be effectively,
or actually, a type constructor. (Might be easier to think about if you
imagine desugaring to syntax like `Both<A, B>` instead of `A + B`). As
elsewhere, a type would be uniquely determined by the combination of its
type constructor and its type arguments, so `Foo + Bar` in two separate
places in the program would be the same type, with the same vtable.

Another question is whether `Bar + Foo` would also be the same type.
Looking at what GHC does, it seems `(Show Int, Read Int) ~ (Read Int, Show
Int)` does *not* hold, so they are different types, but when it comes to
constraint-solving time, if you know one you can deduce the other, so
unless you are specifically testing for type equality of constraints then
they're equivalent in practice, which seems reasonable.


>
> Note we can already get something similar with
>
>    trait Combine1And2: Trait1 + Trait2 {}
>    impl<T: Trait1 + Trait2> Combine1And2 for T {}
>
>    // use ~Combine1And2
>
> which makes it clear how the vtables work, since Combine1And2 has its own
> vtable explicitly constructed from the two traits.


Yeah I know. It feels a bit like using defunctionalization by hand to
simulate HOFs though. Much nicer if things are first class and there are
simple rules, like: "if T1 and T2 are traits, then T1 + T2 is a trait".
Might even simplify some things in the implementation, e.g. traits would
now only have *one* supertrait, there would now only be *one* trait bound
on each type variable, though I'm not too familiar with it.


> (I guess ~(Trait1 + Trait2) would be most useful if one could cast down to
> ~Trait1 and ~Trait2.)
>
>
>
>>
>>
>> The next few are going to be about higher- (or just different-) kinded
>> generics. To avoid confusion, I'm going to use "built-in trait" to mean
>> things like `Freeze` and `Send`, and "kind" to mean what it does everywhere
>> else in the non-Rustic world.
>>
>> I think the best available syntax for annotating the kinds of types would
>> be to borrow the same or similar syntax as used to declare them, with
>> either `type` or `struct` being the kind of "normal" non-generic types (the
>> only kind of type that current Rust lets you abstract over). [I prefer
>> `type`, because both structs and enums inhabit the same kind.] This is kind
>> of like how C++ does it. For example, the kind of `Result` would be
>> `type<type, type>`. Our `type` corresponds to C++'s `typename` and
>> Haskell's `*`, and `type<type, type>` to C++'s `template<typename,
>> typename> class` and Haskell's `* -> * -> *`. So, for example, you could
>> write the fully kind-annotated signature of an identity function restricted
>> to Result-shaped types (yeah, actually doing this would be pointless!) as:
>>
>>     fn dumb_id<type<type, type> R, type A, type B>(x: R<A, B>) -> R<A, B>;
>>
>> To explicitly annotate the kind of the Self-type of a trait, we could
>> borrow the `for` syntax used in `impl`s. Here's the fully kind-annotated
>> version of the `Functor` trait familiar from Haskell:
>>
>>     trait Functor for type<type> Self {
>>         fn fmap<type A, type B>(a: &Self<A>, f: |&A| -> B) -> Self<B>;
>>     }
>>
>> (Obviously, explicitly annotating every kind would be tiresome, and
>> `Self` is a little redundant when nothing else could go there. I could
>> imagine `trait Functor for type<type>`, `trait Functor for Self`, and/or
>> `trait Functor` all being legal formulations of the above. I'll get back to
>> this later.)
>>
>>
>>
> Could this be:
>
>    fn dumb_id<R<type, type>, A, B>(x: R<A, B>) -> R<A, B>;
>

That seems reasonable. Writing it out even more fully, I expect this would
actually be `type R<type, type>`, but the "return kind" could probably be
inferred/defaulted independently (and here I was going to say that at first
it could only ever be `type` anyways, but instead see below). The advantage
of writing it this way is that it tracks the declaration syntax even more
closely, and perhaps reads a little better. The drawback is that it's not
so obvious any more that you could leave off the kind-information to have
it be inferred, because it's no longer separate from the name of the type
variable; it's also farther away from the C++ way of writing it (though
that's probably not a huge loss). I'm also not sure if it still reads
better after you add a trait bound, e.g. `fn foo<F<type>: Functor>(...)`
versus `fn foo<type<type> F: Functor>(...)`.

What I was wondering in the parentheses above is whether you could have
something `type<type> MT<type<type>>`. In other words whether "templates
can return templates", which you can't do in C++ but can in Haskell. This
is closely tied to the question about currying and partial application at
the type level. If we do have that, then the above is completely equivalent
to `type MT<type<type>, type>` (just like `(* -> *) -> * -> *` and `(* ->
*) -> (* -> *)` are the same thing in Haskell), though it might be nicer to
write it one way or the other in different cases. If we don't have
automatic currying, then this might be a way to do it manually.



>
> and


>    trait Functor<type> {
>        fn fmap<A, B>(a: &Self<A>, f: |&A| -> B) -> Self<B>;
>    }
>

Not quite. This would be equivalent to `class Functor a self` (a MPTC)
rather than `class Functor (f :: * -> *)`, which is what we want.
Distinguishing the parameters of `Self` from the parameters of the trait is
why I borrowed the `impl ... for ...` syntax from impls, which I think is
nice and symmetric.



>
> Then something like  A<type<type>, type> would correspond to A :: (* -> *)
> -> * -> *. Speaking of which, could we just use *, `R<*, *>`, `R<*<*>, *>`,
> `trait Functor<*>`? Although that looks a little cryptic in the nested case.
>

I don't like `*` as the name for this in Haskell either. Various
dependently typed languages call it `Set`, which is closer, but if I were
lord I would name it `Type`. So `type` is pretty much perfect here, IMHO.


-- 
Your ship was destroyed in a monadic eruption.
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to