Project

General

Profile

Actions

Feature #14579

closed

Hash value omission

Added by shugo (Shugo Maeda) about 6 years ago. Updated 7 months ago.

Status:
Closed
Target version:
-
[ruby-core:85950]

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

hash_value_omission.diff (619 Bytes) hash_value_omission.diff shugo (Shugo Maeda), 03/06/2018 01:51 PM

Related issues 4 (0 open4 closed)

Related to Ruby master - Feature #11105: ES6-like hash literalsRejectedActions
Related to Ruby master - Feature #18124: Hash shorthands (matching constructors functionality in JS)ClosedActions
Related to Ruby master - Feature #14973: Proposal of percent literal to expand HashClosedActions
Has duplicate Ruby master - Feature #17292: Hash Shorthand / PunningClosedActions
Actions #1

Updated by shugo (Shugo Maeda) about 6 years ago

Updated by Eregon (Benoit Daloze) about 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) about 6 years ago

Should this also work for non-Symbols keys like:

x = 1
y = 2
h = { "x" => , "y" => }

Updated by phluid61 (Matthew Kerwin) about 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) about 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) about 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) about 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) about 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.

Actions #10

Updated by mame (Yusuke Endoh) over 3 years ago

Actions #11

Updated by mame (Yusuke Endoh) over 2 years ago

  • Related to Feature #18124: Hash shorthands (matching constructors functionality in JS) added

Updated by sorah (Sorah Fukumori) over 2 years ago

  • Status changed from Rejected to Open
  • Assignee set to matz (Yukihiro Matsumoto)

Updated by knu (Akinori MUSHA) over 2 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) over 2 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) over 2 years ago

  • Status changed from Open to Closed

Thank you.
Committed in c60dbcd1c55cd77a24c41d5e1a9555622be8b2b8.

Updated by matz (Yukihiro Matsumoto) over 2 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) over 2 years ago

For the record: { "#{ str }": } is not allowed. Matz said that it is intentional.

Updated by knu (Akinori MUSHA) over 2 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) over 2 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) over 2 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) over 2 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) over 2 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) over 2 years ago

Note that constants are also allowed:

X = 1
p(X:) #=> {:X=>1}

Updated by shugo (Shugo Maeda) over 2 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) over 2 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 method self.

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) over 2 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) over 2 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 than binding.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) over 2 years ago

@duerst (Martin Dürst)

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) over 2 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) over 2 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) over 2 years ago

  • Status changed from Assigned to Closed

Matz accepted the current behavior at DevelopersMeeting20210916Japan

Updated by knu (Akinori MUSHA) over 2 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.

Actions #33

Updated by hsbt (Hiroshi SHIBATA) over 2 years ago

  • Related to Feature #14973: Proposal of percent literal to expand Hash added

Updated by olivierlacan (Olivier Lacan) 7 months 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.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like1Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0