Feature #12282
openHash#dig! for repeated applications of Hash#fetch
Description
A new feature for your consideration: #dig! which is to #fetch as #dig is to #[]. For me and maybe many others, Hash#fetch is used much more than Hash#[]. And traversing multiple fetches isn't very convenient nor Ruby-like, e.g.: places.fetch(:countries).fetch(:canada).fetch(ontario).
Here's how it would work:
places = { countries: { canada: true } }
places.dig :countries, :canada # => true
places.dig! :countries, :canada # => true
places.dig :countries, :canada, :ontario # => nil
places.dig! :countries, :canada, :ontario # => KeyError: Key not found: :ontario
Here's an implementation and tests: https://gist.github.com/dogweather/819ccdb41c9db0514c163cfdb1c528e2
Updated by sawa (Tsuyoshi Sawada) over 8 years ago
This makes sense only within limited cases, i.e. when the same key never appears at different depths. For example, if you get an error:
hash.dig!(:foo, :bar, :foo) # => KeyError: Key not found: :foo
you cannot tell whether the :foo
at the first depth or the third depth (or both) is missing. In such case, there is not much difference from doing:
hash[:foo][:bar][:foo] # => NoMethodError: undefined method `[]' for nil:NilClass
from the point of view of information the error provides. (With dig!
, all you can tell is that the error was not caused by :bar
.) I do not see much value in having a method for such limited use case.
Updated by nobu (Nobuyoshi Nakada) over 8 years ago
- Description updated (diff)
I'm negative because:
-
This example is wrong.
places.dig :countries, :canada, :ontario # => nil
It raises a
TypeError
. -
It feels curious to me that the method with '!' raises an exception whereas the method without '!' doesn't.
Updated by nobu (Nobuyoshi Nakada) over 8 years ago
Nobuyoshi Nakada wrote:
It raises a
TypeError
.
So you have the method which raises an exception already.
Updated by sawa (Tsuyoshi Sawada) over 8 years ago
Nobuyoshi Nakada wrote:
It raises a
TypeError
.
I think it is a typographical error of
places.dig :countries, :ontario # => nil
places.dig! :countries, :ontario # => KeyError: Key not found: :ontario
Updated by shyouhei (Shyouhei Urabe) over 8 years ago
I don't like the name. It doesn't uniform other usage of bang in method names.
Updated by shevegen (Robert A. Heiler) over 8 years ago
I concur with Shyouhei Urabe - the name seems to not entirely fit the
given outcome.
More typical use cases of methods with ! bang, if we ignore any
exception, would be more akin to things such as:
x = "abc" # => "abc"
x.delete 'c' # => "ab"
x # => "abc"
x.delete! 'c' # => "ab"
x # => "ab"
On the topic of hashes as data structures, assumingly that they
may be more nested than the usual array, I tend to always attempt
to have all hashes and arrays as simple as possible, if and when
that is possible (it is not always possible, see the older
discussions about before .dig was added; but dig! is a weird name,
we want to obtain something, not change the data structure right?).
Updated by k0kubun (Takashi Kokubun) about 7 years ago
How about this name?
places.deep_fetch(:countries, :canada, :ontario)
I've encountered the case which I did "places.fetch(:countries).fetch(:canada).fetch(:ontario)" multiple times. I want this method.
Updated by robb (Robb Shecter) almost 6 years ago
Thanks everyone, for the discussion. I realize that my original comparison with #dig had a typographical error. Here's a gem implementation, with examples that are correct: https://github.com/dogweather/digbang
require 'dig_bang'
places = {
world: {
uk: true,
usa: true
}
}
# No difference when the key exists
places.dig :world, :uk # true
places.dig! :world, :uk # true
# A relevant error when the key is missing
places.dig :world, :uk, :alaska # nil
places.dig! :world, :uk, :alaska # KeyError: Key not found: :alaska
About the method name with the bang. I see that this might be more of a Rails naming convention, which !
methods perform the same action, but throw an error instead of returning a nil
on failure. And that's exactly my intent with dig!
vs. dig
. Basically, a "checked" dig. I don't think that Ruby has a naming convention for an alternate interface which throws an exception vs. return a nil. (?)
module DigBang
def self.fetch_all(fetchable, keys)
keys.reduce(fetchable) { |a, e| a.fetch(e) }
end
end
class Hash
def dig!(*keys)
DigBang.fetch_all(self, keys)
end
end
class Array
def dig!(*keys)
DigBang.fetch_all(self, keys)
end
end
Updated by robb (Robb Shecter) almost 6 years ago
Another naming idea is #fetch_all
, signalling that this is essentially a #fetch
over a list of keys.
Updated by k0kubun (Takashi Kokubun) almost 6 years ago
- Has duplicate Feature #15563: #dig that throws an exception if a key doesn't exist added
Updated by k0kubun (Takashi Kokubun) over 5 years ago
- Has duplicate Feature #14602: Version of dig that raises error if a key is not present added
Updated by alanwu (Alan Wu) 10 days ago
- Has duplicate Feature #20815: Fetch for nested hash added