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 []
.
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)
@ozra [FooSubtype.new].as(Array(Foo))
errors so I really don't think that's the correct syntax.
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]
.
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)
@RX14 - I meant to _add_ support for that with that "operator".
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.
@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.
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.
@samueleaton It's not a method, it's syntax. Same as class
and module
, they are not methods.
@RX14, yes it might lead to confusion.
See also thread on the google group.
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.
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.
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.
Wow. No one said one word about the :=
notation I suggested. Thanks.
@trans well my mother always taught me if you have nothing nice to say, say nothing at all.
@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.
@trans maybe bring it up on the mailing list...
@trans Woah, I'm pretty sure that was an attempt at humor, not an insult.
@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.
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.
@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).
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]
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
Any update on the decision?
The of
syntax isn't always user-friendly, sometimes parentheses are required: https://github.com/crystal-lang/crystal/issues/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{}
.
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.
I’m in favour of removing it when I have to spent sometime deciding which is my preference would be a time wasting.
@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"
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.
@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
).)
Most helpful comment
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 docall &.as(T)
. But changingof
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)