Feature #19069
closedDefault value assignment with `Hash.new` in block form
Description
This is a spin-out from #19063, and is a recapture of my comment https://bugs.ruby-lang.org/issues/19063#note-15.
I propose to change the behavior of Hash.new
when it takes a block with its parameter signature absent. In such case, the evaluated value of the block should be assigned to the hash with the missing key in question (h3
below). When the block does take parameters, then the behavior should remain as is now (h1
, h2
below).
h1 = Hash.new{|h, k| h[k] = "foo"}
h2 = Hash.new{|h, k| "foo"}
h3 = Hash.new{"foo"}
h1[:a] # => "foo"
h2[:a] # => "foo"
h3[:a] # => "foo"
h1 # => {:a=>"foo"}
h2 # => {}
h3 # => {:a=>"foo"}
This will solve a few problems. First, as discussed in #19063, many users make the mistake of writing Hash.new([])
when they actually mean Hash.new{|h, k| h[k] = []}
, and I suspect this is partially due to the fact that the block {|h, k| h[k] = []}
is too long, and the users are tempted to avoid writing so. With the proposed feature introduced, the users will be encouraged to naturally write the correct form Hash.new{[]}
.
Second, some of the more advanced users than those mentioned above still make the mistake of writing Hash.new{foo}
when they actually mean Hash.new{|h, k| h[k] = foo}
, and the current proposal is to let them actually be equivalent.
Third, this will resemble a similar construct Array.new(5){[]}
and they will make a good parallel.
Indeed, there are situations where the intended behavior is to just run a routine without assigning a new key-value pair to the hash. Examples of current code may be as follows:
h4 = Hash.new{some_routine_to_take_care_of_the_missing_key_situation}
h5 = Hash.new{raise WrongKeyBlahBlahError}
Such blocks would need to be prepended by a parameter signature |h, k|
in order to avoid unwanted results. I do not think such transition would be a huge pain.
Updated by nobu (Nobuyoshi Nakada) about 2 years ago
Adding a new element in the default proc will add by just referencing.
I'm not for doing it implicitly, and ditto for #19063.
Updated by sawa (Tsuyoshi Sawada) about 2 years ago
@nobu (Nobuyoshi Nakada) (Nobuyoshi Nakada) I interpret that your concern is that the hash will grow endlessly by random referencing (unintentionally or by an attack). I have some possible alternative modifications to the proposal for that.
-
Warn when a block without parameter signature
Hash.new{"foo"}
is used. -
Forget about looking at the block arity, and explicitly control the behavior as below:
h1 = Hash.new{|h, k| "foo"} h2 = Hash.new{"foo"} h3 = Hash.new(assign: true){|h, k| "foo"} h4 = Hash.new(assign: true){"foo"} h1[:a] # => "foo" h2[:a] # => "foo" h3[:a] # => "foo" h4[:a] # => "foo" h1 # => {} h2 # => {} h3 # => {:a=>"foo"} h4 # => {:a=>"foo"}
This will decrease the usefulness of the proposal a little bit for the use case mentioned in the proposal, but writing
Hash.new(assign: true){"foo"}
may still be a bit handier than writingHash.new{|h, k| h[k] = "foo"}
. -
Implement this feature under a separate method
Hash.new!
. Users will be aware that using this method can be potentially dangerous.
Updated by hsbt (Hiroshi SHIBATA) almost 2 years ago
- Related to Feature #19061: Proposal: make a concept of "consuming enumerator" explicit added
Updated by hsbt (Hiroshi SHIBATA) almost 2 years ago
- Related to deleted (Feature #19061: Proposal: make a concept of "consuming enumerator" explicit)
Updated by hsbt (Hiroshi SHIBATA) almost 2 years ago
- Related to Feature #19063: Hash.new with non-value objects should be less confusing added
Updated by matz (Yukihiro Matsumoto) almost 2 years ago
- Status changed from Open to Rejected
For compatibility's sake (and to avoid extra complexity), we reject this idea.
Matz.
Updated by Eregon (Benoit Daloze) almost 2 years ago
As a note this change could be useful to make the default_proc assignment thread-safe (avoid assigning twice for the same key).
It's not safe for Hash
currently (I'll file a separate issue to discuss that) and neither for Concurrent::Hash
(https://github.com/ruby-concurrency/concurrent-ruby/issues/970#issuecomment-1346338557).