Feature #14579
closedHash value omission
Description
How about to allow value omission in Hash literals:
x = 1
y = 2
h = {x:, y:}
p h #=> {:x=>1, :y=>2}
And in keyword arguments:
def login(username: ENV["USER"], password:)
p(username:, password:)
end
login(password: "xxx") #=> {:username=>"shugo", :password=>"xxx"}
Files
Updated by shugo (Shugo Maeda) over 6 years ago
- Related to Feature #11105: ES6-like hash literals added
Updated by shugo (Shugo Maeda) over 6 years ago
- File hash_value_omission.diff hash_value_omission.diff added
Updated by Eregon (Benoit Daloze) over 6 years ago
I find this syntax very confusing.
The two occurrences of password:
above means two very different things (required kwarg and auto-hash creation).
For
x = 1
y = 2
h = {x:, y:}
it looks to me like the values are missing.
I'd prefer a syntax which is different than "key syntax without value", and refers to the variable name used for the value more clearly, like:
x = 1
y = 2
h = {x, y}
That would also work for the second case like so:
def login(username: ENV["USER"], password:)
p({username, password})
end
Updated by Eregon (Benoit Daloze) over 6 years ago
Should this also work for non-Symbols keys like:
x = 1
y = 2
h = { "x" => , "y" => }
Updated by phluid61 (Matthew Kerwin) over 6 years ago
Eregon (Benoit Daloze) wrote:
I'd prefer a syntax which is different than "key syntax without value", and refers to the variable name used for the value more clearly, like:
x = 1 y = 2 h = {x, y}
Please no, this is too close to perl's weird handling of lists/hashes. To me it reads like you're trying to write:
h = {1=>2}
Updated by shevegen (Robert A. Heiler) over 6 years ago
I agree with Matthew.
I understand the suggestion trying to make the syntax even more succinct
(less to type) but I think it's one step too much. Ruby already has a
quite condensed syntax.
I think this syntax here, asked by Benoit, is also problematic:
h = { "x" => , "y" => }
Has a slight "visual" problem, at the least to me. I would expect =>
to "point" to something on the right hand side, which the normal
syntax in hashes, in ruby, requires (unless you use the foo: :bar
syntax notation).
The:
h = {x:, y:}
to my brain it's indeed a bit confusing because I would normally
expect something on the right side of "foo: ".
Updated by shugo (Shugo Maeda) over 6 years ago
Eregon (Benoit Daloze) wrote:
I'd prefer a syntax which is different than "key syntax without value", and refers to the variable name used for the value more clearly, like:
x = 1 y = 2 h = {x, y}
I proposed the above syntax in #11105, but it was rejected, and this proposal is alternative.
Updated by matz (Yukihiro Matsumoto) over 6 years ago
I prefer this syntax to #11105, but this introduces a Ruby-specific syntax different from ES6 syntax.
Besides that, I don't like both anyway because they are not intuitive (for me).
Matz.
Updated by shugo (Shugo Maeda) over 6 years ago
- Status changed from Open to Rejected
matz (Yukihiro Matsumoto) wrote:
I prefer this syntax to #11105, but this introduces a Ruby-specific syntax different from ES6 syntax.
Besides that, I don't like both anyway because they are not intuitive (for me).
So I withdraw this proposal.
Updated by mame (Yusuke Endoh) about 4 years ago
- Has duplicate Feature #17292: Hash Shorthand / Punning added
Updated by mame (Yusuke Endoh) about 3 years ago
- Related to Feature #18124: Hash shorthands (matching constructors functionality in JS) added
Updated by sorah (Sorah Fukumori) about 3 years ago
- Status changed from Rejected to Open
- Assignee set to matz (Yukihiro Matsumoto)
Updated by knu (Akinori MUSHA) about 3 years ago
Here's a typical use case.
def get_user_profile(client)
client.get_json("/current_user") => { id: }
client.get_json("/profile", { id: }) => { nick:, bio: }
return { id:, nick:, bio: }
end
Updated by matz (Yukihiro Matsumoto) about 3 years ago
After the RubyKaigi 2021 sessions, we have discussed this issue and I was finally persuaded.
Our mindset has been updated (mostly due to mandatory keyword arguments).
Accepted.
Matz.
Updated by shugo (Shugo Maeda) about 3 years ago
- Status changed from Open to Closed
Thank you.
Committed in c60dbcd1c55cd77a24c41d5e1a9555622be8b2b8.
Updated by matz (Yukihiro Matsumoto) about 3 years ago
I assumed the value should be a local variable. The merged patch calls the method when the local variable is not defined.
I doubt this is sufficient behavior. Any opinion?
Matz.
Updated by mame (Yusuke Endoh) about 3 years ago
For the record: { "#{ str }": }
is not allowed. Matz said that it is intentional.
Updated by knu (Akinori MUSHA) about 3 years ago
We should allow it to call a (private) method if no variable with the name defined. We use methods in RSpec or with attr_reader that look like variables, and programmers don't necessarily distinguish between methods from variables when writing a program. I believe this syntax should take methods into account.
Updated by baweaver (Brandon Weaver) about 3 years ago
knu (Akinori MUSHA) wrote in #note-18:
We should allow it to call a (private) method if no variable with the name defined. We use methods in RSpec or with attr_reader that look like variables, and programmers don't necessarily distinguish between methods from variables when writing a program. I believe this syntax should take methods into account.
I would agree that (private) methods are very useful here, especially attr_*
methods. There are a few cases I would wonder what they do:
-
@var:
- Would this work with instance/class/global/constant variables if they're valid symbols? -
a = 1; {a:, b: 3}
- Does it support mixing omissions and regular values? -
p a:, b: 3
- Does it work with implied hashes / keywords? (I think yes).
I agree that { "#{ str }": }
should not be allowed, as it presents potential for abuse and vulnerabilities.
I've PR'd the second case on mixed values, but just considered the first with ivars and similar concepts. I'm not sure which way that should go.
Updated by knu (Akinori MUSHA) about 3 years ago
We also discussed further with Matz and concluded that quoted keys ({ "key": }
) are not allowed with or without interpolation. This is simply because you don't need that when any local variable or constant can be written without quotation, and because it might make you feel it could possibly mean { "key": "key" }
and that would be confusing.
Updated by knu (Akinori MUSHA) about 3 years ago
baweaver (Brandon Weaver) wrote in #note-19:
@var:
- Would this work with instance/class/global/constant variables if they're valid symbols?
No, because we didn't change the symbol key syntax. { @var: @var }
is not valid, so { @var: }
isn't either. The same goes for $var
and @@var
.
a = 1; {a:, b: 3}
- Does it support mixing omissions and regular values?
Yes.
p a:, b: 3
- Does it work with implied hashes / keywords? (I think yes).
Yes, but beware when you omit the last value without the closing parenthesis. The interpreter will look further past the line end for a value.
Updated by shugo (Shugo Maeda) about 3 years ago
- Status changed from Closed to Assigned
matz (Yukihiro Matsumoto) wrote in #note-16:
I assumed the value should be a local variable. The merged patch calls the method when the local variable is not defined.
I doubt this is sufficient behavior. Any opinion?
I believe a method should be called when a local variable is not defined.
Because it's convenient as knu stated, and because {x:}
is a syntax sugar of {x: x}
except that keywords are allowed.
Updated by shugo (Shugo Maeda) about 3 years ago
Note that constants are also allowed:
X = 1
p(X:) #=> {:X=>1}
Updated by shugo (Shugo Maeda) about 3 years ago
shugo (Shugo Maeda) wrote in #note-22:
except that keywords are allowed.
I meant that keywords are allowed as local variable or method names.
For example, {if:}
is not a syntax error and {self:}
doesn't access the receiver but a local variable or method self
.
Updated by duerst (Martin Dürst) about 3 years ago
shugo (Shugo Maeda) wrote in #note-24:
I meant that keywords are allowed as local variable or method names.
For example,{if:}
is not a syntax error and{self:}
doesn't access the receiver but a local variable or methodself
.
Ah, so {if:}
means something close to {if: local_variable_get(:if)}
and '{self:}means something close to
{self: local_variable_get(:self)}(and NOT
{self: self}`). Not sure we need this, but also not sure it hurts.
Updated by shugo (Shugo Maeda) about 3 years ago
duerst (Martin Dürst) wrote in #note-25:
Ah, so
{if:}
means something close to{if: local_variable_get(:if)}
and '{self:}means something close to
{self: local_variable_get(:self)}(and NOT
{self: self}`).
Yes.
Technically speaking, send(:if)
is used instead of local_variable_get if the local variable is not defined.
Not sure we need this, but also not sure it hurts.
In the meeting just after RubyKaigi, someone pointed out that {if:}[:if]
is faster than binding.local_variable_get(:if)
.
excelsior:ruby$ cat bm.rb
require "benchmark"
Benchmark.bmbm do |b|
->(if:) {
b.report("binding.local_variable_get") do
10000.times do
binding.local_variable_get(:if)
end
end
b.report("new hash syntax") do
10000.times do
{if:}[:if]
end
end
}.call(if: 123)
end
excelsior:ruby$ ./ruby bm.rb
Rehearsal --------------------------------------------------------------
binding.local_variable_get 0.005680 0.000211 0.005891 ( 0.005889)
new hash syntax 0.001817 0.000136 0.001953 ( 0.001965)
----------------------------------------------------- total: 0.007844sec
user system total real
binding.local_variable_get 0.003668 0.000094 0.003762 ( 0.003763)
new hash syntax 0.000829 0.000162 0.000991 ( 0.001042)
Updated by duerst (Martin Dürst) about 3 years ago
shugo (Shugo Maeda) wrote in #note-26:
duerst (Martin Dürst) wrote in #note-25:
Technically speaking,
send(:if)
is used instead of local_variable_get if the local variable is not defined.Not sure we need this, but also not sure it hurts.
In the meeting just after RubyKaigi, someone pointed out that
{if:}[:if]
is faster thanbinding.local_variable_get(:if)
.
I don't think using if
as the name of a local variable is a good idea, and I don't think the speed of bad ideas should concern us too much.
Updated by zverok (Victor Shepelev) about 3 years ago
I don't think using
if
as the name of a local variable is a good idea,
It is good (and widely used, BTW) name for a method parameter, in contexts like
validate :foo, if: :something?
I don't see how it is bad idea, while producing the clearest method call convention for "conditional" DSLs.
Updated by shugo (Shugo Maeda) about 3 years ago
duerst (Martin Dürst) wrote in #note-27:
I don't think using
if
as the name of a local variable is a good idea, and I don't think the speed of bad ideas should concern us too much.
As zverok stated, a keyword such as if
is used as a keyword argument (especially on Rails?).
Updated by Dan0042 (Daniel DeLorme) about 3 years ago
matz (Yukihiro Matsumoto) wrote in #note-16:
I assumed the value should be a local variable.
I also assumed the same thing, but after getting over my initial surprise I found this way is quite nice, very ruby-ish. A bit like learning that rescue => obj.attr
is valid.
Updated by shugo (Shugo Maeda) about 3 years ago
- Status changed from Assigned to Closed
Matz accepted the current behavior at DevelopersMeeting20210916Japan
Updated by knu (Akinori MUSHA) about 3 years ago
...Which is that { symbol: }
verbosely means { symbol: binding.local_variable_defined?(:symbol) ? binding.local_variable_get(:symbol) : __send__(:symbol) }
with no exception, no matter if the symbol is if
, self
, fork
, return
or whatever.
Updated by hsbt (Hiroshi SHIBATA) almost 3 years ago
- Related to Feature #14973: Proposal of percent literal to expand Hash added
Updated by olivierlacan (Olivier Lacan) about 1 year ago
Has it been considered to ever expand this feature to allow instance variables and global variables. Or was it strictly reserved for local variables intentionally, to avoid incompatible or dangerous behavior?
Thanks for adding this regardless, it's a wonderful feature.