Project

General

Profile

Feature #11747

"bury" feature, similar to 'dig' but opposite

Added by dam13n (damien sutevski) almost 5 years ago. Updated 7 months ago.

Status:
Rejected
Priority:
Normal
Target version:
-
[ruby-core:71709]

Description

In Matz's recent Rubyconf talk, he used this example for the new 'dig' feature coming in Ruby 2.3:

# we want this
data[:users][0][:name]

# we can do this w/o nil errors
data.dig(:users, 0, :name)

What I'm proposing is a 'bury' feature that is the opposite of 'dig' in a sense. It inserts a value at an arbitrary depth, for example:

data.bury(:users, 0, :name, 'Matz')

This will create a nested hash or an array automatically at each step if it doesn't already exist, and that can be inferred from the what the user is passing (such as a symbol or string for a hash or an integer for an array). It's similar to autovivification but more powerful!

This behavior is very common, at least in my experience, so a dry method built into Ruby would be awesome!


Files

bury_examples.rb (1 KB) bury_examples.rb briankung (Brian Kung), 05/17/2018 04:36 PM

Related issues

Has duplicate Ruby master - Feature #13179: Deep Hash Update MethodRejectedActions

Updated by nobu (Nobuyoshi Nakada) almost 5 years ago

  • Description updated (diff)
  • Status changed from Open to Feedback

How can it know what should be created, hash, array, or struct?

Updated by sawa (Tsuyoshi Sawada) almost 5 years ago

inferred from the what the user is passing (such as a symbol or string for a hash or an integer for an array)

I don't think this is a good idea. I think it should rather depend on the class of the receiver.

{}.bury(:users, 0, :name, 'Matz') # => {:users => {0 => {:name => "Matz"}}}
[].bury(:users, 0, :name, 'Matz') # => error
{}.bury(0, 1, 2, :foo) # => {0 => {1 => {2 => :foo}}}
[].bury(0, 1, 2, :foo) # => [[nil, [nil, nil, :foo]]]

and similar for struct.

Updated by dam13n (damien sutevski) almost 5 years ago

Tsuyoshi Sawada wrote:

inferred from the what the user is passing (such as a symbol or string for a hash or an integer for an array)

I don't think this is a good idea. I think it should rather depend on the class of the receiver.

{}.bury(:users, 0, :name, 'Matz') # => {:users => {0 => {:name => "Matz"}}}
[].bury(:users, 0, :name, 'Matz') # => error
{}.bury(0, 1, 2, :foo) # => {0 => {1 => {2 => :foo}}}
[].bury(0, 1, 2, :foo) # => [[nil, [nil, nil, :foo]]]

and similar for struct.

I agree. I should clarify that I was assuming the class of the receiver (data) was known in my example. The inference I was talking about was that a buried 0 would imply an array position by default instead of a hash key. But if it was strictly determined by the receiver class, that'd still be useful.

Updated by matz (Yukihiro Matsumoto) almost 5 years ago

  • Status changed from Feedback to Rejected

It's not clear to generate either Hash, Array, or Struct (or whatever) to bury a value.
So it's better to reject now.

Matz.

#5

Updated by nobu (Nobuyoshi Nakada) over 3 years ago

Updated by briankung (Brian Kung) over 2 years ago

matz (Yukihiro Matsumoto) wrote:

It's not clear to generate either Hash, Array, or Struct (or whatever) to bury a value.

Would it be desirable to specify the new object in a block? That would make it somewhat symmetrical to how Hash.new takes a block as a default value. For example:

[{users: ['skipped']].bury(:users, 1, :name, 'Matz') { Hash.new }
# [{:users => ['skipped', {:name => 'Matz'}]}]

{users: {0 => nil}}.bury(:users, 0, :name, 'Matz') { |next_arg| Struct.new(next_arg).new }
# {:users => {0 => #<struct name="Matz">}}
#
# If the one of the retrieved values is nil, in this case {0 => nil},
# should #bury overwrite it?

If this is okay, then it might even be nice if #dig took a block as well as a fallback value:

[].dig(1) { 'default' }
#=> "default"

Additional #bury examples have been attached as bury_examples.rb.

Updated by bkatzung (Brian Katzung) over 1 year ago

Much of this has been available through my XKeys gem since Q2 2014.

data = {}.extend XKeys::Auto # Vs ::Hash, uses arrays for int keys
data[:users, 0, :name] # nil
data[:users, 0, :name, :raise => true] # KeyError
data[:users, :[], :name] = 'Matz' # :[] is next index, 0 in this case
# {:users=>[{:name=>"Matz"}]}
pick = [:users, 0, :name]
data[*pick] # Matz
data[:users, 0, :accesses, :else => 0] += 1
# {:users=>[{:name=>"Matz", :accesses=>1}]}

Updated by cvss (Kirill Vechera) 10 months ago

A proposal to specify the path for bury with classes as values of a hash arg:

{}.bury(users: Array, 0 => Hash, name: Hash, something: 'Value') # {user: [{name: {something: 'Value'}]}

So all absent nodes could be created via klass.new

Updated by cvss (Kirill Vechera) 10 months ago

A one-liner alternative for hash-only cases can be implemented using Enumerable#reduce:

root = {}
[:a, :b, :c].reduce(root){@1[@2]||={}}[:d] = 'E' # root => {:a=>{:b=>{:c=>{:d=>"E"}}}}

Updated by schwad (Nick Schwaderer) 7 months ago

I think the issues/problems specified in the comments are not present with a Hash-only implementation. :)

I would be supportive of re-considering this feature just for use with a Hash, where I believe 80% of the real-life use cases would (and do) exist. I have encountered this need before in the wild, but not with Arrays. In fact, I'm only here because it seems like something one would 'expect' ruby already to do.

Thanks much for hearing me out.

Also available in: Atom PDF