On Sun, Feb 2, 2014 at 6:08 PM, Benjamin Striegel <[email protected]>wrote:

> After sleeping on it I'm not convinced that this would be a net
> improvement over our current situation. With a few caveats I'm really
> rather happy with the syntax as it is.
>
>
> On Sun, Feb 2, 2014 at 8:55 AM, Jason Fager <[email protected]> wrote:
>
>> I'm not a huge fan of this proposal.  It makes declarations longer, and
>> it removes the visual consistency of Foo<T,U> everywhere, which I think
>> introduces its own pedagogical issue.
>>
>> The recent addition of default type parameters, though, makes me think
>> there's a reasonable change that increases consistency and shortens
>> declarations in a few common cases.
>>
>> From what I understand, the reason we can't just have
>>
>> impl Trait<T> for Foo<T,U>
>>
>> is because it's ambiguous whether T and U are intended to be concrete or
>> generic type names; i.e.,
>>
>> impl<T> Trait<T> for Foo<T,U>
>>
>> tells the compiler that we expect U to be a concrete type name.
>>
>> Our new default type parameter declarations look like:
>>
>> struct Foo<T,U=Bar>
>>
>> So what if to actually make generic types concrete, we always used the
>> '='?
>>
>> struct Foo<T,U=Bar>
>> impl Trait<T> for Foo<T, U=Derp>
>>
>> This saves a character over 'impl<T> Trait<T> for Foo<T, Derp>', solves
>> the greppability problem, and makes intuitive sense given how defaults are
>> declared.
>>
>> It also has a nice parallel with how ':' is used - ':' adds restrictions,
>> '=' fully locks in place.  So what is today something like
>>
>> impl<T:Ord> Trait<T> for Foo<T, Derp>
>>
>> would become
>>
>> impl Trait<T:Ord> for Foo<T, U=Derp>
>>
>> The rule would be that the first use of a type variable T would introduce
>> its bounds, so for instance:
>>
>> impl Trait<T:Ord> for Foo<Z:Clone, U=Derp>
>>
>> would be fine, and
>>
>> impl Trait<T> for Foo<T:Clone, U=Derp>
>>
>> would be an error.
>>
>> More nice fallout:
>>
>> struct Foo<A,B>
>> impl Foo<A,B=Bar> {
>>     fn one(a: A) -> B
>>     fn two(a: A) -> B
>>     fn three(a: A) -> B
>> }
>>
>> means that if I ever want to go back and change the name of Bar, I only
>> have to do it in one place, or if Bar is actually some complicated type, I
>> only had to write it once, like a little local typedef.
>>
>> I'm sure this has some glaring obvious flaw I'm not thinking of.  It
>> would be nice to have less syntax for these declarations, but honestly I'm
>> ok with how it is now.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>> On Sat, Feb 1, 2014 at 5:39 PM, Corey Richardson <[email protected]>wrote:
>>
>>> Hey all,
>>>
>>> bjz and I have worked out a nice proposal[0] for a slight syntax
>>> change, reproduced here. It is a breaking change to the syntax, but it
>>> is one that I think brings many benefits.
>>>
>>> Summary
>>> =======
>>>
>>> Change the following syntax:
>>>
>>> ```
>>> struct Foo<T, U> { ... }
>>> impl<T, U> Trait<T> for Foo<T, U> { ... }
>>> fn foo<T, U>(...) { ... }
>>> ```
>>>
>>> to:
>>>
>>> ```
>>> forall<T, U> struct Foo { ... }
>>> forall<T, U> impl Trait<T> for Foo<T, U> { ... }
>>> forall<T, U> fn foo(...) { ... }
>>> ```
>>>
>>>
>From a readability point of view, I am afraid this might be awkward though.

Coming from a C++, I have welcome the switch from `typedef` to `using`
(aliases) because of alignment issues; consider:

typedef std::map<int, std::string> MapType;
typedef std::vector<std::pair<int, std::string>> VectorType;

vs

using MapType = std::map<int, std::string>;
using VectorType = std::vector<std::pair<int, std::string>>;

In the latter, the entities being declared are at a constant offset from
the left-hand margin; and close too; whereas in the former, the eyes are
strained as they keep looking for what is declared.


And now, let's look at your proposal:

fn foo(a: int, b: int) -> int { }

fn foo<T, U>(a: T, b: U) -> T { }

forall<T, U> fn foo(a: T, b: U) -> T { }

See how "forall" causes a "bump" that forces you to start looking where
that name is ? It was so smooth until then !


So, it might be a net win in terms of grep-ability, but to be honest it
seems LESS readable to me.

-- Matthieu


> The Problem
>>> ===========
>>>
>>> The immediate, and most pragmatic, problem is that in today's Rust one
>>> cannot
>>> easily search for implementations of a trait. Why? `grep 'impl Clone'` is
>>> itself not sufficient, since many types have parametric polymorphism.
>>> Now I
>>> need to come up with some sort of regex that can handle this. An easy
>>> first-attempt is `grep 'impl(<.*?>)? Clone'` but that is quite
>>> inconvenient to
>>> type and remember. (Here I ignore the issue of tooling, as I do not find
>>> the
>>> argument of "But a tool can do it!" valid in language design.)
>>>
>>> A deeper, more pedagogical problem, is the mismatch between how `struct
>>> Foo<...> { ... }` is read and how it is actually treated. The
>>> straightforward,
>>> left-to-right reading says "There is a struct Foo which, given the types
>>> ...
>>> has the members ...". This might lead one to believe that `Foo` is a
>>> single
>>> type, but it is not. `Foo<int>` (that is, type `Foo` instantiated with
>>> type
>>> `int`) is not the same type as `Foo<unit>` (that is, type `Foo`
>>> instantiated
>>> with type `uint`). Of course, with a small amount of experience or a very
>>> simple explanation, that becomes obvious.
>>>
>>> Something less obvious is the treatment of functions. What does `fn
>>> foo<...>(...) { ... }` say? "There is a function foo which, given types
>>> ...
>>> and arguments ..., does the following computation: ..." is not very
>>> adequate.
>>> It leads one to believe there is a *single* function `foo`, whereas
>>> there is
>>> actually a single `foo` for every substitution of type parameters! This
>>> also
>>> holds for implementations (both of traits and of inherent methods).
>>>
>>> Another minor problem is that nicely formatting long lists of type
>>> parameters
>>> or type parameters with many bounds is difficult.
>>>
>>> Proposed Solution
>>> =================
>>>
>>> Introduce a new keyword, `forall`. This choice of keyword reads very
>>> well and
>>> will not conflict with any identifiers in code which follows the [style
>>> guide](https://github.com/mozilla/rust/wiki/Note-style-guide).
>>>
>>> Change the following declarations from
>>>
>>> ```
>>> struct Foo<T, U> { ... }
>>> impl<T, U> Trait<T> for Foo<T, U> { ... }
>>> fn foo<T, U>(...) { ... }
>>> ```
>>>
>>> to:
>>>
>>> ```
>>> forall<T, U> struct Foo { ... }
>>> forall<T, U> impl Trait<T> for Foo<T, U> { ... }
>>> forall<T, U> fn foo(...) { ... }
>>> ```
>>>
>>> These read very well. "for all types T and U, there is a struct Foo
>>> ...", "for
>>> all types T and U, there is a function foo ...", etc. These reflect that
>>> there
>>> are in fact multiple functions `foo` and structs `Foo` and
>>> implementations of
>>> `Trait`, due to monomorphization.
>>>
>>>
>>> [0]:
>>> http://cmr.github.io/blog/2014/02/01/polymorphic-declaration-syntax-in-rust/
>>> _______________________________________________
>>> Rust-dev mailing list
>>> [email protected]
>>> https://mail.mozilla.org/listinfo/rust-dev
>>>
>>
>>
>> _______________________________________________
>> Rust-dev mailing list
>> [email protected]
>> https://mail.mozilla.org/listinfo/rust-dev
>>
>>
>
> _______________________________________________
> Rust-dev mailing list
> [email protected]
> https://mail.mozilla.org/listinfo/rust-dev
>
>
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to