Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax sugar for prefix-style type parameter lists. #122

Closed
wants to merge 1 commit into from
Closed

Syntax sugar for prefix-style type parameter lists. #122

wants to merge 1 commit into from

Conversation

ben0x539
Copy link

This is a proposal for type parameter lists that come well before the
actual generic items. Example:

type <T: Unwieldy+Traits+Go+Here,
      U: Even+More+Traits> {
  fn foo(t: T) -> U { ... }
  fn bar(u: U) -> T { ... }
}

This is a proposal for type parameter lists that come well before the
actual generic items. Example:

```rust
type <T: Unwieldy+Traits+Go+Here,
      U: Even+More+Traits> {
  fn foo(t: T) -> U { ... }
  fn bar(u: U) -> T { ... }
}
```
@brendanzab
Copy link
Member

I agree this is definitely an issue. I'm not sure if this is the right solution but it is definitely something we should think about. I think @nikomatsakis has a proposal in the works for where clauses that might help solve this.

@emberian
Copy link
Member

@gasche
Copy link

gasche commented Jun 23, 2014

The block behavior that allows to define several values parametrized on the same parameters (which are then implicitly added as extra parameters to the values, instead of requiring a specific accessor syntax as you would in a struct/module) can be convenient; besides C++ templates, it is also the mechanism behind Section in Coq.

That said, the "long unwieldly list of constraints/traits" part would be better solved by simply a constraint definition/aliasing mechanism, as GHC constraint synonyms. Let's call that a "trait synonym". In your example

type <L:LatticeDir + Combine,
      T:Clone + InferStr + LatticeValue,
      V:Clone + Eq + ToStr + Vid + UnifyVid<Bounds<T>>>
pub fn lattice_vars(this: &L,
                    a_vid: V,
                    b_vid: V,
                    lattice_dir_op: LatticeDirOp<T>)
                    -> cres<LatticeVarResult<V,T>> {
    [...]
}

what I actually want to write is a definition of the type-level information that makes a lattice:

trait Lattice<L, T, V> = <
  L: LatticeDir + Combine,
  T: Clone + InferStr + LatticeValue
  V: Clone + Eq + ToStr + Vid + UnifyVid<Bounds<T>>
>

pub fn lattice_vars<(L, T, V): Lattice>
                   (this: &L,
                    a_vid: V,
                    b_vid: V,
                    lattice_dir_op: LatticeDirOp<T>)
                   -> cres<LatticeVarResult<V,T>> {
    [...]
}

It does not only avoid redundancy: by introducing a name, it also increases readability.

(Interestingly, Lattice is defined as a trait not on a single
type, but on three type variables simulatenously, with a nice
pattern-matching syntax to destructure it. This makes perfect
sense as a trait on the product kind (★ × ★ × ★).)

PS: one issue with trait synonyms is that it often make sense to give constraints on the same type variable in different place. In this example you may well consider that InferStr and ToStr should not actually be part of the Lattice trait definition, but that only lattice_vars (and not all functions that work on lattices) needs them. You would then write something like fn lattice_vars<(L,T,V): Lattice, T:InferStr, V:ToStr>, or maybe fn lattice_vars<(L, T:InferStr, V:ToStr): Lattice>, either of which are awkward. Haskell does it better by allowing to separate the binding site of the type variable with further constraints on it.

@dobkeratops
Copy link

"Unwieldy+Traits+Go+Here" ... I have become torn between C++ and Rust a little on this sort of thing; I miss the ability to write generic/overloaded functions without having to specify trait-bounds, and I'm a bit worried what it will all look like once there's the ability to overload on multiple parameters and you have to start specifying complex relationships between types. I like the ability to just focus on functions.

Perhaps it would be nice if they were optional (which is what we'll get in C++ once it finally gets concepts) - write code,make it work.. and add bounds wherever they solve more problems than they cause.

IMO the real problem in C++ is header files, where you had to have included the right headers to declare the overloads.. and Rust wouldn't suffer from that problem

There's even a nice proposal for C++ that would make 'auto' sugar for type-params in function argument lists. Rusts' syntax could be even cleaner.

@glaebhoerl
Copy link
Contributor

I like the proposal and was going to propose something similar.

The concrete keyword was a random choice out of the list that sort of fit. Could use a new keyword, could use for or let

for is the obvious choice for me. It directly mirrors Haskell's forall for universal quantification. "For all types T such that ..., declare these items..."

Even in our current for loops, there is an implied "for each foo in ..", and "for each" and "for all" are essentially the same.

Blocks with multiple items after a single prefix-style type parameter list desugar into a series of regular generic items, not inside of any block or mod-like grouping, with that same type parameter list, with the exception that type parameters not used by all items get omitted in the type parameter lists for the items where they are not used. ...
All items in the block must refer to at least one type parameter in the prefix-style type parameter list but must not themselves have a type parameter list, all type parameters in the prefix-style type parameter list must be used by at least one item.

I would rather do a more direct, mechanical translation, for greater simplicity and predictability:

Items within a for<T1..Tn> { ... } block may have any number of their own type parameters. T1..Tn are prepended to each item's type parameter list, regardless of whether or not they are mentioned by that item. Items which cannot have type parameters are illegal within the block. There is a warn-by-default lint for items within the block which don't use all of T1..Tn.

@gasche
Copy link

gasche commented Jul 1, 2014

A comment on how the interaction of "trait synonyms" and the where-clauses RFC #135 : there is a design space regarding how to abbreviate constraints, and the syntax I suggested is only one (and certainly not the best) possible proposal. But in any case, where-clauses would be a very nice thing to improve the use of trait/constraint synonyms.

In the current syntax there is no separation between the place to introduce a parameter, and the place to constrain it with a trait bound. The syntax I suggested above

trait Lattice<L, T, V> = <
  L: LatticeDir + Combine,
  T: Clone + InferStr + LatticeValue
  V: Clone + Eq + ToStr + Vid + UnifyVid<Bounds<T>>
>

allows to give a name to a sequence of binders (that introduce new parameters in scope), because there is currently no other way. But as noted this is awkward, as you may want to give a name to some aspects of the bounds <L,V>, and another name to another aspect of the bounds <V,T>, and this doesn't work as both places are supposed to introduce V in scope. Also, the syntax to use such multiple-binder traits, <(L, T, V): Lattice>, is rather ugly because it is not symmetrical with the syntax used to define the trait (Lattice<L, T, V> = ...).

If RFC #135 is accepted and where clauses become available, you can drop the idea of giving a name to a binding site, and only give a name to the corresponding constraint. You would then write:

constraint Lattice<L, T, V> =
  L: LatticeDir + Combine,
  T: Clone + LatticeValue,
  V: Clone + Eq + Vid + UnifyVid<Bounds<T>>

pub fn lattice_vars<L, T, V>
                   (this: &L,
                    a_vid: V,
                    b_vid: V,
                    lattice_dir_op: LatticeDirOp<T>)
                   -> cres<LatticeVarResult<V,T>>
                   where Lattice<L, T, V>, T:InferStr, V:Str
{
    [...]
}

where Lattice<L,T,V> is now an object of a different syntactic nature (a general constraint rather than a trait), as it does not bind the variables L, T, V, and is defined as a conjunction of constraints (and not a fragment of environment).

The Haskell language historically had an evolution that is reminiscent of the move from traits to a general notion of constraints. At the beginning, the only form of qualified types where type-classes (Ord a, Eq b => ..), so people thought of improving and enriching the ways to add type class requirements. Nowadays there are other notions of constraints in the underlying theory (type equalities, etc.) which are not type-classes, and new language features seek to improve the handling (naming, composition etc.) of arbitrary constraints, rather than only type-classes. The language is better as a result of this generalization.

@glaebhoerl
Copy link
Contributor

I would rather do a more direct, mechanical translation, for greater simplicity and predictability:

Items within a for<T1..Tn> { ... } block may have any number of their own type parameters. T1..Tn are prepended to each item's type parameter list, regardless of whether or not they are mentioned by that item. Items which cannot have type parameters are illegal within the block. There is a warn-by-default lint for items within the block which don't use all of T1..Tn.

I just noticed an analogy: I think this is the same thing that happens for the type parameters introduced by a trait, and its methods. So we can just say that for<T> { ... } and trait Foo<T> { ... } should work the same way.

@alexcrichton
Copy link
Member

Thanks for submitting an RFC! We discussed this at the triage meeting today, and the consensus was to simultaneously close this as postponed an in favor of #135. Where clauses provide much of the same syntactical benefit of grouping together type parameters, and we'd otherwise like to postpone the grouping of type parameters for multiple function scope.

Closing for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
postponed RFCs that have been postponed and may be revisited at a later time.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants