RFC: syntax of multiple trait bounds on a type parameter

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Niko Matsakis
Hi,

Right now we use no delimiter at all to separate multiple trait bounds
for a single type variable:

fn foo<T: Ord Eq Hash>(...) {...}

Originally we thought to use commas, but inserting commas into the list
creates an ambiguity between the comma that separates type parameters,
as illustrated here:

fn foo<T: Ord, Eq, Hash, U: Hash, V>(...) {...}

Marijn and I hashed through all kinds of delimeters and failed to find
one, and hence he settled on spaces. It seemed like a good idea at the time.

However, over time, I have found that this syntax is very hard for me to
parse, visually speaking. Moreover, Patrick recently observed that there
is an ambiguity when the trait names are multi-component paths:

fn foo<T: Ord ::Eq>(...) {...}

Does this indicate one bound `Ord::Eq` or two bounds `Ord` and `::Eq`?
The current fix for this is to make the tokenizer treat `id::`
differently from `id ::` (note the separating space in the latter
example). The first is 1 token. The second is 2 tokens. Actually, I
think this behavior already existed, and I can't remember why?I think it
had something to do with the flexible treatment of keywords that we used
to have. @brson, do you remember?

Anyway, it was proposed on IRC today that we could do something like
this instead:

fn foo<T:Eq>(..) {...} // One bound is the same
fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require parentheses

I find this visually appealing. It's easier for my eye to read,
particularly if the bounds are complicated.

I know it's a syntax change, and we're trying to avoid those, but I
thought I'd throw it out for a wider audience to ponder, particularly as
it would eliminate a rather surprising whitespace dependency.



Niko

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Lucian Branescu
Whitespace dependency is painful in the long term.

This looks nice, the only downside is that changing between one and two
traits requires editing in two places.. Perhaps if parens were merely
optional with one trait and <T:(Eq)> was valid this would be mitigated
somewhat.


On 11 January 2013 18:50, Niko Matsakis <niko at alum.mit.edu> wrote:

> Hi,
>
> Right now we use no delimiter at all to separate multiple trait bounds for
> a single type variable:
>
> fn foo<T: Ord Eq Hash>(...) {...}
>
> Originally we thought to use commas, but inserting commas into the list
> creates an ambiguity between the comma that separates type parameters, as
> illustrated here:
>
> fn foo<T: Ord, Eq, Hash, U: Hash, V>(...) {...}
>
> Marijn and I hashed through all kinds of delimeters and failed to find
> one, and hence he settled on spaces. It seemed like a good idea at the time.
>
> However, over time, I have found that this syntax is very hard for me to
> parse, visually speaking. Moreover, Patrick recently observed that there is
> an ambiguity when the trait names are multi-component paths:
>
> fn foo<T: Ord ::Eq>(...) {...}
>
> Does this indicate one bound `Ord::Eq` or two bounds `Ord` and `::Eq`? The
> current fix for this is to make the tokenizer treat `id::` differently from
> `id ::` (note the separating space in the latter example). The first is 1
> token. The second is 2 tokens. Actually, I think this behavior already
> existed, and I can't remember why?I think it had something to do with the
> flexible treatment of keywords that we used to have. @brson, do you
> remember?
>
> Anyway, it was proposed on IRC today that we could do something like this
> instead:
>
> fn foo<T:Eq>(..) {...} // One bound is the same
> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require parentheses
>
> I find this visually appealing. It's easier for my eye to read,
> particularly if the bounds are complicated.
>
> I know it's a syntax change, and we're trying to avoid those, but I
> thought I'd throw it out for a wider audience to ponder, particularly as it
> would eliminate a rather surprising whitespace dependency.
>
>
>
> Niko
> ______________________________**_________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/**listinfo/rust-dev<https://mail.mozilla.org/listinfo/rust-dev>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130111/da603291/attachment.html>

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Niko Matsakis


Lucian Branescu wrote:
> This looks nice, the only downside is that changing between one and
> two traits requires editing in two places.. Perhaps if parens were
> merely optional with one trait and <T:(Eq)> was valid this would be
> mitigated somewhat.

I was assuming they'd be optional for the single trait case.  For the
reason you state, I also considered the possibility of writing <T(Eq)>
instead of <T:Eq>, but that seemed like a strictly larger change that
also has implications for our "impl Type: Trait" syntax (which of course
@pcwalton wants to change to "impl Trait for Type").


Niko

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Graydon Hoare
In reply to this post by Niko Matsakis
On 11/01/2013 10:50 AM, Niko Matsakis wrote:

> Anyway, it was proposed on IRC today that we could do something like
> this instead:
>
> fn foo<T:Eq>(..) {...} // One bound is the same
> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require parentheses
>
> I find this visually appealing. It's easier for my eye to read,
> particularly if the bounds are complicated.
>
> I know it's a syntax change, and we're trying to avoid those, but I
> thought I'd throw it out for a wider audience to ponder, particularly as
> it would eliminate a rather surprising whitespace dependency.

I'm ok with this, and much less fond of solving the ambiguity by adding
whitespace dependency there. IME a multi-trait-bound signature is a bit
of a code smell anyways.

(Incidentally, this sort of thing is exactly why I feel like we need
someone to start formalizing the grammar. It's not really ok to say
"we've frozen the syntax" before we know that the existing grammar is
unambiguous!)

-Graydon



Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

John Clements

On Jan 11, 2013, at 11:25 AM, Graydon Hoare wrote:

> On 11/01/2013 10:50 AM, Niko Matsakis wrote:
>
>> Anyway, it was proposed on IRC today that we could do something like
>> this instead:
>>
>> fn foo<T:Eq>(..) {...} // One bound is the same
>> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require parentheses
>>
>> I find this visually appealing. It's easier for my eye to read,
>> particularly if the bounds are complicated.
>>
>> I know it's a syntax change, and we're trying to avoid those, but I
>> thought I'd throw it out for a wider audience to ponder, particularly as
>> it would eliminate a rather surprising whitespace dependency.
>
> I'm ok with this, and much less fond of solving the ambiguity by adding
> whitespace dependency there. IME a multi-trait-bound signature is a bit
> of a code smell anyways.
>
> (Incidentally, this sort of thing is exactly why I feel like we need
> someone to start formalizing the grammar. It's not really ok to say
> "we've frozen the syntax" before we know that the existing grammar is
> unambiguous!)

+1, possibly volunteering :).

John


Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Brian Anderson
In reply to this post by Niko Matsakis
On 01/11/2013 10:50 AM, Niko Matsakis wrote:
> Hi,
>
> Right now we use no delimiter at all to separate multiple trait bounds
> for a single type variable:
>
> fn foo<T: Ord Eq Hash>(...) {...}

Supertraits share the same syntax, so we should consider them together.
If or when impls can implement multiple traits they may also need a
similar syntax.

trait Foo: Ord Eq Hash { }

>
> Originally we thought to use commas, but inserting commas into the
> list creates an ambiguity between the comma that separates type
> parameters, as illustrated here:
>
> fn foo<T: Ord, Eq, Hash, U: Hash, V>(...) {...}
>
> Marijn and I hashed through all kinds of delimeters and failed to find
> one, and hence he settled on spaces. It seemed like a good idea at the
> time.
>
> However, over time, I have found that this syntax is very hard for me
> to parse, visually speaking. Moreover, Patrick recently observed that
> there is an ambiguity when the trait names are multi-component paths:
>
> fn foo<T: Ord ::Eq>(...) {...}
>
> Does this indicate one bound `Ord::Eq` or two bounds `Ord` and `::Eq`?
> The current fix for this is to make the tokenizer treat `id::`
> differently from `id ::` (note the separating space in the latter
> example). The first is 1 token. The second is 2 tokens. Actually, I
> think this behavior already existed, and I can't remember why?I think
> it had something to do with the flexible treatment of keywords that we
> used to have. @brson, do you remember?

No, but it's the ugliest part of the lexer. Let's make it go away.

>
> Anyway, it was proposed on IRC today that we could do something like
> this instead:
>
> fn foo<T:Eq>(..) {...} // One bound is the same
> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
> parentheses

As an example, this is why the parens are needed:

fn foo<T:(Ord, Eq, Hash), U>(...) {...}

so you _could_ still allow `foo<T: Ord, Eq, Hash>` since there's no
ambiguity, but that's more complex that necessary.

Here's how it looks like in a trait:

trait Foo:(Ord, Eq, Hash) { }

The parens aren't necessary here for disambiguation. Would we want them
anyway?

With the parens, the colons aren't necessary so you could instead have:

fn foo<T(Eq)>(...) {...}
fn foo<T(Ord, Eq, Hash), U>(...) {...}

>
> I find this visually appealing. It's easier for my eye to read,
> particularly if the bounds are complicated.

It does contain many new tokens though.

>
> I know it's a syntax change, and we're trying to avoid those, but I
> thought I'd throw it out for a wider audience to ponder, particularly
> as it would eliminate a rather surprising whitespace dependency.

One thing to consider is that presumably trait inheritance will
eventually make multi-trait bounds less common. It could even replace
multi-trait bounds entirely, though I don't think that's a great idea.


Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Brian Anderson
On 01/11/2013 11:50 AM, Brian Anderson wrote:

> On 01/11/2013 10:50 AM, Niko Matsakis wrote:
>> Hi,
>>
>> Right now we use no delimiter at all to separate multiple trait
>> bounds for a single type variable:
>>
>> fn foo<T: Ord Eq Hash>(...) {...}
>
> Supertraits share the same syntax, so we should consider them
> together. If or when impls can implement multiple traits they may also
> need a similar syntax.
>
> trait Foo: Ord Eq Hash { }
>
>>
>> Originally we thought to use commas, but inserting commas into the
>> list creates an ambiguity between the comma that separates type
>> parameters, as illustrated here:
>>
>> fn foo<T: Ord, Eq, Hash, U: Hash, V>(...) {...}
>>
>> Marijn and I hashed through all kinds of delimeters and failed to
>> find one, and hence he settled on spaces. It seemed like a good idea
>> at the time.
>>
>> However, over time, I have found that this syntax is very hard for me
>> to parse, visually speaking. Moreover, Patrick recently observed that
>> there is an ambiguity when the trait names are multi-component paths:
>>
>> fn foo<T: Ord ::Eq>(...) {...}
>>
>> Does this indicate one bound `Ord::Eq` or two bounds `Ord` and
>> `::Eq`? The current fix for this is to make the tokenizer treat
>> `id::` differently from `id ::` (note the separating space in the
>> latter example). The first is 1 token. The second is 2 tokens.
>> Actually, I think this behavior already existed, and I can't remember
>> why?I think it had something to do with the flexible treatment of
>> keywords that we used to have. @brson, do you remember?
>
> No, but it's the ugliest part of the lexer. Let's make it go away.
>
>>
>> Anyway, it was proposed on IRC today that we could do something like
>> this instead:
>>
>> fn foo<T:Eq>(..) {...} // One bound is the same
>> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
>> parentheses
>
> As an example, this is why the parens are needed:
>
> fn foo<T:(Ord, Eq, Hash), U>(...) {...}
>
> so you _could_ still allow `foo<T: Ord, Eq, Hash>` since there's no
> ambiguity, but that's more complex that necessary.

Of course, there _is_ an ambiguity, this would just be requiring parens
to add the second type parameter, so not a good idea.

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Gareth Smith
In reply to this post by Niko Matsakis
On 11/01/13 18:50, Niko Matsakis wrote:
>
> fn foo<T:Eq>(..) {...} // One bound is the same
> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
> parentheses

How about using { ... } rather than ( ... ), like imports:

use xxx::{a, b, c};

fn foo<T:{Ord, Eq, Hash}>(...) { ... }

I don't know that this is better but maybe it is worth considering?

Gareth.

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

James Gao
and how about these two case:

a) fn foo<T1: Ord, Eq, Hash; T2: Ord, ::Eq> (...) {...}

b) fn foo<T1: Ord + Eq + Hash, T2: Ord + ::Eq> (...) {...}


On Sat, Jan 12, 2013 at 6:27 AM, Gareth Smith
<garethdanielsmith at gmail.com>wrote:

> On 11/01/13 18:50, Niko Matsakis wrote:
>
>>
>> fn foo<T:Eq>(..) {...} // One bound is the same
>> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
>> parentheses
>>
>
> How about using { ... } rather than ( ... ), like imports:
>
> use xxx::{a, b, c};
>
> fn foo<T:{Ord, Eq, Hash}>(...) { ... }
>
> I don't know that this is better but maybe it is worth considering?
>
> Gareth.
>
> ______________________________**_________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/**listinfo/rust-dev<https://mail.mozilla.org/listinfo/rust-dev>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130112/087e461c/attachment.html>

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Matthieu Monrocq
On Sat, Jan 12, 2013 at 3:21 AM, James Gao <gaozm55 at gmail.com> wrote:

> and how about these two case:
>
> a) fn foo<T1: Ord, Eq, Hash; T2: Ord, ::Eq> (...) {...}
>
> b) fn foo<T1: Ord + Eq + Hash, T2: Ord + ::Eq> (...) {...}
>
>
Really likes b), + looks especially suiting since we are adding up
requirements.

-- Matthieu


>
> On Sat, Jan 12, 2013 at 6:27 AM, Gareth Smith <garethdanielsmith at gmail.com
> > wrote:
>
>> On 11/01/13 18:50, Niko Matsakis wrote:
>>
>>>
>>> fn foo<T:Eq>(..) {...} // One bound is the same
>>> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
>>> parentheses
>>>
>>
>> How about using { ... } rather than ( ... ), like imports:
>>
>> use xxx::{a, b, c};
>>
>> fn foo<T:{Ord, Eq, Hash}>(...) { ... }
>>
>> I don't know that this is better but maybe it is worth considering?
>>
>> Gareth.
>>
>> ______________________________**_________________
>> Rust-dev mailing list
>> Rust-dev at mozilla.org
>> https://mail.mozilla.org/**listinfo/rust-dev<https://mail.mozilla.org/listinfo/rust-dev>
>>
>
>
> _______________________________________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/listinfo/rust-dev
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130112/cf8889b2/attachment.html>

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

James Boyden
In reply to this post by James Gao
On Sat, Jan 12, 2013 at 1:21 PM, James Gao <gaozm55 at gmail.com> wrote:
> and how about these two case:
>
> a) fn foo<T1: Ord, Eq, Hash; T2: Ord, ::Eq> (...) {...}

I think that a problem with using semicolon as the delimiter between
trait type parameters (i.e., between `T1: X` and `T2: Y`) is that it
would differ subtly and unexpectedly from the use of comma in function
definitions and structures (i.e., "everywhere else").

As a result, you would have:
        fn f(a: X, b: Y) ...
        struct Z {a: X, b: Y}
versus:
        fn foo<T2: X; T2: Y> ...

> b) fn foo<T1: Ord + Eq + Hash, T2: Ord + ::Eq> (...) {...}

Similarly to `+`, perhaps `|` or `&` could make sense as the delimiter
between trait bounds within a single type variable, if you interpret
(or define) the combination of trait bounds as a set operation:
either "the union of the constraints specified by the bounds" or
"the intersection of types that meet the constraints".

        fn foo<T1: Ord | Eq | Hash, T2: Ord | ::Eq> (...) {...}
        fn foo<T1: Ord & Eq & Hash, T2: Ord & ::Eq> (...) {...}

jb


> On Sat, Jan 12, 2013 at 6:27 AM, Gareth Smith <garethdanielsmith at gmail.com>
> wrote:
>>
>> On 11/01/13 18:50, Niko Matsakis wrote:
>>>
>>>
>>> fn foo<T:Eq>(..) {...} // One bound is the same
>>> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
>>> parentheses
>>
>>
>> How about using { ... } rather than ( ... ), like imports:
>>
>> use xxx::{a, b, c};
>>
>> fn foo<T:{Ord, Eq, Hash}>(...) { ... }
>>
>> I don't know that this is better but maybe it is worth considering?
>>
>> Gareth.
>>
>> _______________________________________________
>> Rust-dev mailing list
>> Rust-dev at mozilla.org
>> https://mail.mozilla.org/listinfo/rust-dev
>
>
>
> _______________________________________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/listinfo/rust-dev
>

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Benjamin Striegel
In reply to this post by James Gao
> fn foo<T1: Ord + Eq + Hash, T2: Ord + ::Eq> (...) {...}

I heavily prefer using + to wrapping in parens. That said, what does ::Eq
even mean? Is it possible to avert all this discussion by changing ::Eq to
..Eq or something? I've never seen this syntax used before.


On Fri, Jan 11, 2013 at 9:21 PM, James Gao <gaozm55 at gmail.com> wrote:

> and how about these two case:
>
> a) fn foo<T1: Ord, Eq, Hash; T2: Ord, ::Eq> (...) {...}
>
> b) fn foo<T1: Ord + Eq + Hash, T2: Ord + ::Eq> (...) {...}
>
>
> On Sat, Jan 12, 2013 at 6:27 AM, Gareth Smith <garethdanielsmith at gmail.com
> > wrote:
>
>> On 11/01/13 18:50, Niko Matsakis wrote:
>>
>>>
>>> fn foo<T:Eq>(..) {...} // One bound is the same
>>> fn foo<T:(Ord, Eq, Hash)>(...) {...} // Multiple bounds require
>>> parentheses
>>>
>>
>> How about using { ... } rather than ( ... ), like imports:
>>
>> use xxx::{a, b, c};
>>
>> fn foo<T:{Ord, Eq, Hash}>(...) { ... }
>>
>> I don't know that this is better but maybe it is worth considering?
>>
>> Gareth.
>>
>> ______________________________**_________________
>> Rust-dev mailing list
>> Rust-dev at mozilla.org
>> https://mail.mozilla.org/**listinfo/rust-dev<https://mail.mozilla.org/listinfo/rust-dev>
>>
>
>
> _______________________________________________
> Rust-dev mailing list
> Rust-dev at mozilla.org
> https://mail.mozilla.org/listinfo/rust-dev
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130115/d110734e/attachment.html>

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Niko Matsakis


Benjamin Striegel wrote:
>
> I heavily prefer using + to wrapping in parens. That said, what does
> ::Eq even mean? Is it possible to avert all this discussion by
> changing ::Eq to ..Eq or something? I've never seen this syntax used
> before.

FWIW the only two directly comparable scenarios I can think of are Java
and Scala.  Java uses `&` for this same purpose and (presumably) for
this same reason.  Scala uses `with`.  I'd be fine with `+` or `&`.


Niko

Reply | Threaded
Open this post in threaded view
|

RFC: syntax of multiple trait bounds on a type parameter

Brian Anderson
In reply to this post by Matthieu Monrocq
On 01/12/2013 04:51 AM, Matthieu Monrocq wrote:

>
>
> On Sat, Jan 12, 2013 at 3:21 AM, James Gao <gaozm55 at gmail.com
> <mailto:gaozm55 at gmail.com>> wrote:
>
>     and how about these two case:
>
>     a) fn foo<T1: Ord, Eq, Hash; T2: Ord, ::Eq> (...) {...}
>
>     b) fn foo<T1: Ord + Eq + Hash, T2: Ord + ::Eq> (...) {...}
>
>
> Really likes b), + looks especially suiting since we are adding up
> requirements.

Agree, + looks like a nice solution.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130115/9f09d240/attachment.html>