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

Switch <> back to [] #148

Closed
wants to merge 11 commits into from
Closed

Conversation

thehydroimpulse
Copy link

Here we go.

Thoughts?

// Possible syntax for HKTs.
pub trait Monad[M[T]] {
// ...
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this bring the ability to have nicer syntax for HKTs? Wouldn't the example be exactly the same when written like this?

pub trait Monad<M<T>> {
    // ...
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is one syntax proposal for HKTs (following in Scala's footsteps) which is ok (There are better alternatives that focus more on inference). I find [] a lot more composable than <> (i.e., you can nest them without it being cryptic to read).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t see this as a separate point from the second point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define "cryptic to read". The nesting properties of [] and <> are identical. You personally may simply have more experience reading nested [] due to being used to Scala. Me, not being a Scala programmer, very rarely encounter nested [] and I find there to be no benefit at all over <>.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, imo, [] is much nicer to read than <> without being exposed too much myself (to the [] syntax).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this claim should be removed—it has no substance apart from the previous claim, is subjective and is not dealing with a concrete situation (HKT is far future).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixed!


4. There's precendence for it. Scala's syntax for generics is awesome. It imposes very little effort (I think) when reading and understanding.

6. Because it's consistent and has no ambiguities, one can finally use motions like `%` in Vim (and alternatives in other editors.).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never had an issue using % in Vim. We do not support the use of < or > as an operator inside of a generic type parameter list, which means there is no problem finding the matching < or > when using %.

@chris-morgan
Copy link
Member

One of the key benefits of using [] rather than <> is that the delimiters are always matching. The use of < and > for comparison operators means that text editors cannot without semantic knowledge treat < and > in generics and type definitions as matching, significantly hindering the modification of such code; in Vim, for example, one cannot in Rust code use the % motion on generics, which is often a real nuisance.

I am surprised by how much easier I find [] to read than <>; the shape of the characters separates the different words more effectively, somehow.

[] are easier to type on most keyboards than <> (lower versus upper).

Using [] may introduce confusion with indexing and array literals.

@lilyball
Copy link
Contributor

lilyball commented Jul 1, 2014

I don't buy the matching argument. I will readily admit that it's true that [] are always matching, but since we don't support < or > operators inside of type parameter lists, the < and > tokens within a type parameter list are always matching as well. This means that there should be no problem with using % in Vim.

That said, I appear to have broken something, because I swear it used to work for me out of the box, but right now % in Vim is not treating </> as a delimiter. I'm not sure how that would have worked though, as we can't put <:> in 'matchpairs' without causing unexpected beeping when typing an unbalanced >. But we certainly can write our own % operator for the Rust vim file and fix it that way.

@steveklabnik
Copy link
Member

Big 👎 here. <>s are just as nice looking as [], and have more precident.

}
```

4. There's precendence for it. Scala's syntax for generics is awesome. It imposes very little effort (I think) when reading and understanding.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the first sentence, this is pure subjectiveness and not distinct from the earlier points.

What is of note here is this. At the time when Rust changed from [] to <>, there was no known precedent in a C-style language for []-generics: C++ and Java, for example, used <>. Since then, [] has been adopted in at least one fairly popular language, Scala.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

@thehydroimpulse
Copy link
Author

I think the original issue (from what I heard, don't quote me) to switch was because [] was pretty alien at the time. Now, not so much.


Recently there has been a lot of talks on simplifying the syntax. Starting from removing the sigils `@` and `~` and making lifetimes less syntax heavy (through various proposals). I think changing the current generic syntax to `[]` will make it that much better and clearer (I think `[]` is much easier to read).

1. `[]` is easier to type than `<>`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. It actually takes less hand movement for me to type <T> on my US English keyboard than for me to type [T].

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<> needs a shift operator (at least for me), while [] doesn't.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thehydroimpulse You should specify on most keyboards; you simply can’t make such assertions without appropriate guard conditions.

@kballard On the standard US English keyboard, [] don’t require the Shift modifier to enter them whereas <> do. The impact of this is reduced for [ as it will normally be followed by an uppercase letter, incidentally, but it won’t for ['a and it won’t for ] unless the last character was also uppercase. This, I presume, is what you are referring to here: for <T> you can just hold the Shift key for all three characters, whereas with [T] you can’t. I accept this case. Still, in most cases I find [] a little easier to type than <>.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chris-morgan Fixed. Thanks!

@emberian
Copy link
Member

emberian commented Jul 1, 2014

To parse this, when we see a [ after an identifier in an expression, we'll need to defer parsing everything inside until the matching ] until we know the type of the identifier. For example:

foo[bar]

Could be either indexing or taking the value of a generic function, substituting a type parameter, but you won't know.

@thehydroimpulse
Copy link
Author

@cmr Yes there's some ambiguities around arrays with this proposal.


1. `[]` is easier to type than `<>` on *most* keyboards.

2. `[]` delimeters are always matching.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be clarified, with reference to the comparison operators < et al. It should be merged in with the sixth point, the practical benefit of this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!


This is a very easy change to make.

## Downsides
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/##/#/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!


# Detailed design

This is a very easy change to make.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s… not much of a detailed design. How about saying something like “in type parameter lists, replace < with [ and > with ].” and giving examples of T<U, …> and f::<T, …>()?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The

@chris-morgan
Copy link
Member

@cmr: foo[uint]() has been removed from the proposal—it is back to being purely a change from <> to [], and thus you would write foo::[uint]().

@lilyball
Copy link
Contributor

lilyball commented Jul 2, 2014

We can't use () for indexing because we're moving in the direction of having traits that would allow you to make a single value both callable (with ()) and indexable (with []). Which is to say, you could implement a custom closure type that also supports indexing.

@ghost
Copy link

ghost commented Jul 2, 2014

@kballard () for indexing interests me quite a bit. Can you please elaborate your reasoning why Rust can not apply it? Can you tell an example where there is a need to be able to both call and index a value?

@glaebhoerl
Copy link
Contributor

As far as the ugliness of the current type application syntax is concerned, how do people feel about, instead of,

let n = foo::<int>();

writing

let n = foo<type int>();

?

I don't believe type is currently legal in the expression grammar, so I think this should be unambiguous.

It's not shorter, but it's more readable and perhaps less ugly.

@bachm
Copy link

bachm commented Jul 2, 2014

@glaebhoerl I don't like foo::<int>() because it's an exception to the usual way to do things. This is true for foo<type int>() as well.

@simias
Copy link

simias commented Jul 2, 2014

@glaebhoerl how would that scale for more complex declarations? Do you put type in front of each parameter? What about nesting?

Also rust might end up adding stuff like constant integer parameters and things like that, "type" wouldn't fit those semantically. What would you do with lifetime parameters? I guess you could say there're a type but that's stretching it a bit IMO.

I don't think this solves anything really, I don't like the ::< "hack" but I like <type> even less...

@bachm
Copy link

bachm commented Jul 2, 2014

Foo::<int>::f() is also something I dislike. Learning the subtleties of type parameter definitions took me way longer than it should have, and I was already used to C++ syntax. In the long run it's not a big deal, but it feels like poor design.

@glaebhoerl
Copy link
Contributor

I never implied that it's perfect, only that it might suck less.

@simias I was thinking the rule would be that if providing a kind annotation (type, static, trait, maybe ') on the first argument makes it syntactically unambiguous, you can omit the ::, but :: would also remain an option. This presupposes a system for kind annotations which we don't have yet, which is why I didn't want to get into it.

@adrientetar
Copy link

:: isn't ugly imo, short and concise and implies a hierarchy just like paths: out of Foo, take the code path for ints.

@simias
Copy link

simias commented Jul 2, 2014

@adrientetar that's more of a rationalization really. I mean, you can think about it that way but to me :: implies scoping.

But anyway, this is off topic since this proposal still requires :: for disambiguation.

@lilyball
Copy link
Contributor

lilyball commented Jul 2, 2014

@almale RFC 34 defines traits that can be used for implementing the indexing operator on arbitrary types (we actually already have an existing trait for this, but it's badly-designed and nobody uses it).

RFC PR #114 provides unboxed closures by adding traits to represent function calling. This allows arbitrary types to be callable (as foo()).

Implementing both traits on the same type allows a value of that type to be both indexable and callable. This requires that the indexing and calling operators be syntactically distinct, which means () cannot be used for indexing.

@lilyball
Copy link
Contributor

lilyball commented Jul 2, 2014

@almale I don't know what a real-life example of such a type will be, but the fact remains that our traits allow for it, and therefore () cannot be used for both indexing and calling. We have no way to express a restriction that a type may be indexable or may be callable but not both.

Furthermore, you cannot resolve that by getting rid of indexing in favor of using calling as the indexing operator, because that doesn't support the distinction between immutable and mutable indexing, or the ability to assign to an index. Note that while the callable traits have variants for mutable and immutable, one type is not allowed to implement both (or more accurately, a() cannot be used as sugar for the trait method if more than one of the traits are implemented on the value).

@liigo
Copy link
Contributor

liigo commented Jul 2, 2014

C uses [] for index arrary. I don't think there is enough reasons to change
this.
2014年7月2日 下午3:29于 "Almale" notifications@github.com写道:

@kballard https://github.com/kballard () for indexing interests me
quite a bit. Can you please elaborate your reasoning why Rust can not apply
it? Can you tell an example where there is a need to be able to both call
and index a value?


Reply to this email directly or view it on GitHub
#148 (comment).

@boggle
Copy link

boggle commented Jul 3, 2014

Hi,

could you explain the use case for this? I can't see it. While scala
allows that combination I've never used it in practice. So I would be
quite natural for me to not allow both or rather treat indexing as a
special case of callability and only allow implementing one callability
signature trait per target type. Under this rule, [] works awesomely for
types.

Stefan

Kevin Ballard mailto:notifications@github.com
2. Juli 2014 21:59

@almale https://github.com/Almale RFC 34
https://github.com/rust-lang/rfcs/blob/master/active/0034-index-traits.md
defines traits that can be used for implementing the indexing operator
on arbitrary types (we actually already have an existing trait for
this, but it's badly-designed and nobody uses it).

RFC PR #114 #114 provides
unboxed closures by adding traits to represent function calling. This
allows arbitrary types to be callable (as |foo()|).

Implementing both traits on the same type allows a value of that type
to be both indexable and callable. This requires that the indexing and
calling operators be syntactically distinct, which means |()| cannot
be used for indexing.


Reply to this email directly or view it on GitHub
#148 (comment).

@lexspoon
Copy link

lexspoon commented Jul 3, 2014

Interestingly, RFC #34 and RFC PR #114 are quite similar to how Scala approaches these problems. One difference is that Scala doesn't require using any particular trait; a type just defines methods of the appropriate name if it wants to use () syntax. Another difference is that Rust uses different identifiers for calling (call, call_share, call_once) and for indexing (index, index_mut). Scala uses the the same identifiers in both cases (apply, update).

I would think, as @boggle suggests, it's up to you whether you want to have objects that are callable and indexable with a different meaning for each one. If that's more of an accident than an actual goal, then couldn't you just use the same set of names for both cases? In rvalue position, expand to (call, call_share, call_once). In lvalue position, it could be (call_mut, call_share_mut, call_once_mut). This is a syntactic transformation that happens before type checking.

Not to say that it's right because Scala does it, or even that it's right at all for Rust. The two languages run into similar challenges due to both trying to bring functional programming to mainstream ecosystems, so I thought I'd share.

@DAddYE
Copy link

DAddYE commented Jul 22, 2014

In merit to this issue see this: http://www.reddit.com/r/rust/comments/2bbeqe/it_started_out_with_great_enthusiasm_but_ended_up/

We had a small conversation here about one statement from the author: http://www.reddit.com/r/rust/comments/2bbeqe/it_started_out_with_great_enthusiasm_but_ended_up/cj3v5x6

Anyway, I'm one of them that wish we could switch back to [, both I think are better suited and easy to parse mentally.

I don't see that much the point that of c++, I think is something can be mastered within few minutes. Instead I see it problematic for a beginner mentally recognize:

impl<T: Add<T, T>> Add<Vec2<T>, Vec2<T>> for Vec2<T>

@flying-sheep
Copy link

I really think readability should beat familiarity.

Your example is a good one, as it's very jarring to mentally parse, because
most fonts render <> even smaller than “o” (floating above the baseline),
while they would render (){}[] with full height or even more (as low as “g”
and higher than “L”)

There are angled brackets in Unicode, but (understandably) we don't want to
require the user to be able to type them. So braces, brackets, and
parentheses should be the only enclosing characters in this language that
only has ASCII.

The characters <> really only serve as operators, not fake brackets.

@lilyball
Copy link
Contributor

I must admit to being more sympathetic now to the "it looks nicer" argument for [] than I was initially. I still think that's really the only valid argument (all the other ones have been addressed already). I don't know if a purely subjective "nicer" look is worth the unfamiliarity, but I am willing to be convinced. I do worry, as @chris-morgan pointed out initially, that there is a potential confusion with array literals and indexing, but I don't know if that will turn out to be an issue in practice.

@DAddYE
Copy link

DAddYE commented Jul 22, 2014

@kballard regarding that, I'll quote JouMaSePLoS:

That's a weak argument: () denotes both function application and grouping, and yet no-one ever complains about that. It turns out we're all good at contextual parsing, and I bet that if Rust changed generic syntax to use [] you'd get used to it within a few days.

What we're not good at it, objectively, is inferring nesting. In fact with human language we're notoriously bad at it, and no natural utterances have more than a couple levels of nesting. We need artificial assistance to deal with nesting, and [] is just visually objectively better for deep nesting owing to the height and boxlike nature of the brackets.

Anecdotal data: Mathematica/Wolfram Language uses [] for function application, and () only for grouping. And I now much prefer that convention to everything else -- after you are used to it, [] is hands down the most 'visually parseable' delimiter.

Also, as others pointed out, scala use []: http://www.scala-lang.org/old/node/113
However they use () for index, which isn't that bad if you consider that now we can't slice (IIRC) like my_array[1:2], but we must do my_array.slice(1,2) so, my_array.slice(1) or my_array(1) doesn't look that odd.

However, keep in mind that a language syntax last 20+ years, so I really hope we couldn't carry for 40 years the operator >.

@lilyball
Copy link
Contributor

@DAddYE

That's a weak argument: () denotes both function application and grouping, and yet no-one ever complains about that.

That's quite a good point. Though I don't agree with the implication of the latter half of the comment that [] is a better function application operator than ().

@glaebhoerl
Copy link
Contributor

I must admit to being more sympathetic now to the "it looks nicer" argument for [] than I was initially. [...] I do worry, as @chris-morgan pointed out initially, that there is a potential confusion with array literals and indexing, but I don't know if that will turn out to be an issue in practice.

I was very confused when I first saw Scala code using [] for generics. I hadn't encountered that choice of syntax before, and it was only much later that I learned what it meant.

Once again, I think () has all the benefit of [] in terms of readability, minus the drawback of semantic confusion.

Parentheses are in fact the most intuitively correct option, as generic types, or types parameterized over types, in a very precise sense are like functions, or values parameterized over values. (Or not even "like": they are functions at the type level. More specifically, they're the same thing on the type level as enum variants are on the value level. General type functions, or associated types, are the precise analogy for general value-level functions, of which "normal" generic types are a specific case in the same way that enum variant construction is of function calls.)

It's not by accident that Haskell shares the same syntax for function calls, data construction (enum variants), and type construction (generics).

(As a side note: I think we could then use foo@(T1, T2, ...) in place of foo::<T1, T2, ...> for type application at the value level. I think the "at" sense of @ is more appropriate than the "in" of ::. But it looks stupid with angle brackets.)

@lilyball
Copy link
Contributor

@glaebhoerl We can't use (). We already use those in type constructors. Some::(init)(3i) is just too confusing.

@o11c
Copy link
Contributor

o11c commented Jul 22, 2014

In a language that had made different fundamental choices about inference and overloading, () could work, but [] works as it is.

It's not considered a bad thing in any language that supports operator overloading that it's possible to overload both 'function call' and 'index' operators depending on what your intention is. And even though maps are functions, templates are still more like maps than they are like other functions.

@glaebhoerl
Copy link
Contributor

We can't use (). We already use those in type constructors. Some::(int)(3i) is just too confusing.

"Can't" is a strong word. D does it. (They use ! to distinguish type argument lists from value-level ones; current Rust uses ::; in my previous comment I suggested @.)

I think it's much less bizarre than the clash of [] with indexing.

@DAddYE
Copy link

DAddYE commented Jul 22, 2014

IMHO both [ and ( are fine. At this point even if this issue is closed is a call of the @rust-lang/team. @thehydroimpulse do you think it's worth reopening this?

@thehydroimpulse thehydroimpulse restored the type branch July 29, 2014 01:57
@thehydroimpulse
Copy link
Author

@DAddYE Forgot to reply. I think a member of the core team should way in before this is reopened. I don't want to continue a needless bike shed for the sake of it with a low-chance it's going to be taken seriously.

@thehydroimpulse thehydroimpulse deleted the type branch July 30, 2014 01:18
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
Update and rename 0000-isHtmlSafe.md to 0139-isHtmlSafe.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.