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

Remove "of" syntax #3399

Closed
RX14 opened this issue Oct 9, 2016 · 30 comments
Closed

Remove "of" syntax #3399

RX14 opened this issue Oct 9, 2016 · 30 comments

Comments

@RX14
Copy link
Contributor

RX14 commented Oct 9, 2016

I propose we remove the {} of Foo => Bar syntax from Crystal. We've already removed the foo as Bar syntax, so this syntax specifically sticks out.

The current syntax is useful in two situations: creating empty hashes of a specific type, and specifying hash types when the compiler is too specific.

For example:

{FooSubtype.new => BarSubtype.new} of Foo => Bar

Fortunately, the alternative syntax already exists in the language:

Hash(Foo, Bar){FooSubtype.new => BarSubtype.new}

For empty hashes, simply use Hash(Foo, Bar).new. Empty arrays are much the same.

Unfortunately, a good syntax for specifying array types without of currently doesn't exist, because the "array-like object" syntax uses {} instead of [].

@ozra
Copy link
Contributor

ozra commented Oct 9, 2016

If an alternative could be figured out for Arrays too, then the proposal would be better than the status quo.
Could as be re-used to type it immediately as literal perhaps? [FooSubtype.new].as Array(Foo)

@RX14
Copy link
Contributor Author

RX14 commented Oct 9, 2016

@ozra [FooSubtype.new].as(Array(Foo)) errors so I really don't think that's the correct syntax.

@refi64
Copy link
Contributor

refi64 commented Oct 9, 2016

Here's a thought:

In Dart, you can create a typed array via <Foo>[a, b, c]. Maybe Crystal could do something similar, like (Foo)[a, b, c].

@asterite
Copy link
Member

asterite commented Oct 9, 2016

Before we continue discussing this, it would be nice to point out at least one problem with the current syntax, or how changing it would enhance something.

As far as I remember, we changed as to look like a method so you could do call &.as(T). But changing of just for the sake of it doesn't seem like a good idea. And in fact, [] of String reads well and is Ruby-ish in my opinion (uses English "of" instead of just symbol notation)

@ozra
Copy link
Contributor

ozra commented Oct 10, 2016

@RX14 - I meant to add support for that with that "operator".

@trans
Copy link

trans commented Oct 10, 2016

It's not that the reading of "of" is bad. In that respect it is pretty good. But it is a special keyword in the language and those always add more "cognitive load" to the language (for lack of a better term). Granted its not a huge one in this case, but I think it makes sense to ask if we really need it. Does it really improve the language significantly enough to justify the extra "load"? Does,

h = {} of String => String

Significantly improve matters over,

h = Hash(String, String).new

And, if we feel it does improve matters, it is also reasonable to ask if their might be a way to achieve essentially the same (and perhaps even improve upon it) but in a way that fits into the language better, i.e. without the extra word.

So, for that reason, I think it is at least worth careful exploration of the options.

@RX14
Copy link
Contributor Author

RX14 commented Oct 10, 2016

@ozra My point was that adding support is the wrong thing to do because it's overloading one operator with two semantics, one ofwhich only appears when used on a literal.

My opinion is basically that of @trans. I don't have a good syntax worked out but it somehow feels out-of-place to me, and I tend to use Hash(Foo, Bar).new and Array(Foo).new in my code for empty objects. I was then wondering if there was a sensible way to handle literals without using the of operator.

@samueleaton
Copy link
Contributor

Could someone point me to where in the api docs it defines of when used for arrays? I didn't see it in under the macros and its not an array instance method. I see examples in the array section but I would like to see the definition.

@asterite
Copy link
Member

@samueleaton It's not a method, it's syntax. Same as class and module, they are not methods.

@ozra
Copy link
Contributor

ozra commented Oct 12, 2016

@RX14, yes it might lead to confusion.

@rdp
Copy link
Contributor

rdp commented Oct 13, 2016

See also thread on the google group.

@david50407
Copy link
Contributor

The difference between of and as is that as operate something (to seem foo as type Bar), but of doesn't.

of seems to be a syntax sugar to Hash(K, V).new or Array(T).new, it's good because we can pronounce codes (yes, just "read" the code) like [] of Int as "Array of Int" and {} of String => Int as "Hash of String to Int (pair)"

And if we just don't want to use this syntax sugar, we can just use Array(T).new/Array(T){...} and Hash(K, V).new/Hash(K, V){...} instead, I don't think of can be a problem here.

btw, I think we made as to be a pseudo-method to make as better and more useful, not because it's useless or we just want to remove it.

@trans
Copy link

trans commented Oct 13, 2016

I don't think anyone would disagree that of is readable, at least not on the face of it, but it clearly is an appendage and doesn't have any relation to the rest of the language. It's just tacked on. I think: is a better candidate as we already use it for such in other places. Please see this thread.

P.S. This is the thread @rdp is referring too.

@asterite
Copy link
Member

I don't think we'll be removing or changing the of syntax any time soon.

We did spoke with @waj today about Set{1, 2, 3} looking a bit weird, and in fact right now we can do Bytes[1, 2, 3] so we could do the same thing for providing array-like constructors. And this is actually a bit nicer because the method/macro receives a tuple and you know the initial size in advance, so we could probably drop support for custom array-like notation. But [] of T is short, nice and explicit, so it stays.

@trans
Copy link

trans commented Oct 14, 2016

Wow. No one said one word about the := notation I suggested. Thanks.

@RX14
Copy link
Contributor Author

RX14 commented Oct 14, 2016

@trans well my mother always taught me if you have nothing nice to say, say nothing at all.

@trans
Copy link

trans commented Oct 14, 2016

@RX14 I don't understand. Are you inclined to personally insult me b/c of it? I welcome professional critique of the idea. I thought it was a really good idea. I realize it seems odd at first, but I think it makes sense when you work through it. If I've made some error in thinking I'd like to know. It's one thing to be ignored when you make a minor or even silly suggestion, that happens. But when you make a sincere suggestion that you think has a lot of merit, it really sucks to be summarily ignored.

@rdp
Copy link
Contributor

rdp commented Oct 14, 2016

@trans maybe bring it up on the mailing list...

@refi64
Copy link
Contributor

refi64 commented Oct 14, 2016

@trans Woah, I'm pretty sure that was an attempt at humor, not an insult.

@RX14
Copy link
Contributor Author

RX14 commented Oct 14, 2016

@trans I didn't mean it as a personal insult, I'm sorry. I simply meant that I didn't like the idea, but didn't have much to say about it at the time.

Making a variable which hasn't been set have some value or be operable on seems like an idea which only exists to add the : operator to (it's the only method/operator Undefined has), which then only exists to add the := operator (it only works with x = x : Class). I don't think the addition of the := operator is a worthwhile addition anyway, because it's purpose seems to be exactly the same as .new. A "default value" is simply .new with no args.

Is x := {String => Int8} really better than x = Hash(String, Int8).new? Is x := [Int64] really better than x = Array(Int64).new? Is it worth adding extra syntax for? Most of all, I don't think the syntax looks rubylike, or even has the same meaning as := in other languages. The whole idea just feels like an interesting, but messy, idea shoehorned into crystal.

@trans
Copy link

trans commented Oct 16, 2016

I appreciate that, and I appreciate the assessment of my idea. And I think it is a very good assessment actually. Although, in part I think any rationale can be made to sound a little like "only exist to..." since you have to somehow connect the current language as is to the additional syntax, as opposed to just tacking on something new. But my idea does (or did) have the pretty serious flaw in it of "making a variable which hasn't been set have some value or be operable on". You're right about that.

Thankfully last night I realized I was over thinking it and was able to simplify things, so that part isn't a problem any more. You can read about it here. To summarize, I realized we were already using : as operator for binding (just as = is an operator for both binding and assigning). So all we really need to do is allow : to return a useful representation of it's result and define what happens to that when it is passed to =, namely the = sees the binding is already done, so it only need worry about the assignment side, which it gets based on the binding's type signature.

Is x := {String => Int8} really better than x = Hash(String, Int8).new? Is x := [Int64] really better than x = Array(Int64).new

Personally if you asked me to choose between one or the other based solely on looks I would choose the more concise forms. No question in my mind. It's only that we understand that the long forms are technically simpler (to the compiler) that we even ask, "is it really better?" At least for me. And I think on the whole it must be so, b/c why else would even have of to begin with? And of is definitely shoehorned.

I really think my simplified rationale will appease most of the concerns you expressed. But I can understand if you still feel why bother, just use the .new forms and be done with it. I too would probably rather see of just go, even if nothing else takes it's place.

@RX14
Copy link
Contributor Author

RX14 commented Oct 16, 2016

@trans I agree that of is shoehorned, a hack to get empty literals to work when they should have been removed, that's why I started this issue! However I believe that consistency is really nice. I don't believe that Array or Hash should be treated differently from any other class with generics.

If anything, special forms such as {String => Int8} and [Foo] should be defined as special aliases in the type grammar, like {Foo, Bar} and Foo, Bar -> Baz are already aliases for tuples and procs respectively. Then you simply do x = [Foo].new instead of x = Array(Foo).new. I still don't really like that idea though, because I don't think that removing keystrokes is a worthy cause, or that it increases readability (at least not enough to justify it).

@oprypin
Copy link
Member

oprypin commented Oct 6, 2017

How about untyped array/hash literals? Make this work:

h : Hash(Foo, Bar) = {}
a : Array(Foo) = []

And, coincidentally, a : Array(Int32 | String) = [1, 2, 3]

@ghost
Copy link

ghost commented Oct 6, 2017

one other aspect about of that is not helpful:

arr : Array(Int32) = [1]

def foo(a : Array(Int32))
end
# reads really nice
arr = [1] of Int32

def foo(a : Array(Int32)
end
# reads not nice cause once its Array(bla) and once just bla 

@j8r
Copy link
Contributor

j8r commented Feb 6, 2019

Any update on the decision?
The of syntax isn't always user-friendly, sometimes parentheses are required: #6980

There are three ways to create Arrays and Hashes, too much IMHO.

Examples:

This is the same array:

p [0, "a"]
p Array(String | Int32){0, "a"}
p [0, "a"] of String | Int32

This is the same hash:

p {"a" => 0}
p Hash(String, Int32){"a" => 0}
# Requires parentheses
p ({"a" => 0} of String => Int32)

Ok the of syntax is a tight shorter, but it isn't really as used as [] and {} litterals– I think it's fine to be more consistent.

In fact the main problem of the of syntax is it's a useless magic thing to learn – there is no use outside Array and Hash, in contrary of learning and using Object{}.

@bararchy
Copy link
Contributor

bararchy commented Feb 6, 2019

I'm also in favor of removing this, it makes new programmers mix styles and need to learn one more way to do the same thing.

@proyb6
Copy link

proyb6 commented Feb 6, 2019

I’m in favour of removing it when I have to spent sometime deciding which is my preference would be a time wasting.

@david50407
Copy link
Contributor

@proyb6 just choose which you like, IMO
Every sentence has different suitable phrases/words depends on the context.

I still vote for reading the code easily and naturally while keeping "of"

@j8r
Copy link
Contributor

j8r commented Feb 7, 2019

That's the problem @david50407 , we are losing time deciding about which syntax has to be used. Multiply this by each newcomer.
If there was only one way to do this (with of or without), no debate will even exists.

@david50407
Copy link
Contributor

@j8r I think of is more clearly on definition ({} of Foo => Bar), but Hash.new(Foo, Bar) is more suitable on declaring types (and we can not use of on types now).

I agree with the point of multiply way may cause newcomer confuses, but I like the of-way to put types as suffix (Hash(String, Int32){"a" => 0}/Array(Foo | Bar){Foo.new} sometimes seems annoying that shows types in front of the value while Crystal puts most type declaration as suffix (like a : Int64).)

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

No branches or pull requests