ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:91265] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
@ 2019-01-25 17:07 ` djo.went
  2019-01-25 17:48 ` [ruby-core:91266] " djo.went
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: djo.went @ 2019-01-25 17:07 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been reported by 3limin4t0r (Johan Wentholt).

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__)
      value.public_send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__)
          value.public_send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__)
          value.public_send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91266] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
  2019-01-25 17:07 ` [ruby-core:91265] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist djo.went
@ 2019-01-25 17:48 ` djo.went
  2019-01-25 19:35 ` [ruby-core:91267] " shevegen
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: djo.went @ 2019-01-25 17:48 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by 3limin4t0r (Johan Wentholt).

Description updated

I just discovered that `#dig` also call private methods. I updated the provided examples to do the same.

```Ruby
hash = { b: 'b' }
hash.singleton_method.send(:private, :dig)
{ a: hash }.dig(:a, :b)
#=> 'b'
```

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-76514

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91267] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
  2019-01-25 17:07 ` [ruby-core:91265] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist djo.went
  2019-01-25 17:48 ` [ruby-core:91266] " djo.went
@ 2019-01-25 19:35 ` shevegen
  2019-01-26 13:14 ` [ruby-core:91284] " Ruby-Lang
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: shevegen @ 2019-01-25 19:35 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by shevegen (Robert A. Heiler).


I have no particular pro or con against the feature itself as such; I myself do not use or need .dig so I
can not speak much about it. But I believe one problem with the proposal here is the name.

I think a name such as "dig_e" would be very, very rare to see in ruby. Of course I have no idea how
matz thinks about it, but I would recommend to you to also consider alternative names; or perhaps
let it handle just through arguments, whatever may seem to fit better.

Short names are sometimes really, really great, such as p and pp; but I think one overall concern may
be to not lose too much of the meaning. Off the top of my head, I can only think of FileUtils having 
odd/very short method names, and this is mostly because it sort of "simulates" how coreutils utilities
such as "mkdir -p" and similar work.

If you look at recent changes in ruby, you may notice the :exception key - :e would be shorter than
that too, but I think it may not be a primary goal at all times to be too overly succinct, so if that is
a valid reasoning then I think this may explain why :exception would be used, and no shorter
variant. A similar reasoning could apply to the case here - but again, ultimately you have to see what
matz thinks about it not how others may think about it. :)


----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-76515

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91284] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (2 preceding siblings ...)
  2019-01-25 19:35 ` [ruby-core:91267] " shevegen
@ 2019-01-26 13:14 ` Ruby-Lang
  2019-01-26 13:54 ` [ruby-core:91285] " matz
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: Ruby-Lang @ 2019-01-26 13:14 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by jwmittag (Jörg W Mittag).


shevegen (Robert A. Heiler) wrote:
> I have no particular pro or con against the feature itself as such; I myself do not use or need .dig so I
> can not speak much about it. But I believe one problem with the proposal here is the name.
> 
> I think a name such as "dig_e" would be very, very rare to see in ruby. Of course I have no idea how
> matz thinks about it, but I would recommend to you to also consider alternative names; or perhaps
> let it handle just through arguments, whatever may seem to fit better.

There is a well-established convention in Ruby, when you have a pair of methods that does similar things in different ways, to name them `foo` and `foo!`. For example, `select` and `select!`, `Process::exit` and `Process::exit!`, and so on.

So, one possibility would be `dig!`.

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-76532

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91285] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (3 preceding siblings ...)
  2019-01-26 13:14 ` [ruby-core:91284] " Ruby-Lang
@ 2019-01-26 13:54 ` matz
  2019-01-26 14:35 ` [ruby-core:91286] " takashikkbn
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: matz @ 2019-01-26 13:54 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by matz (Yukihiro Matsumoto).


I am against `dig!` for this purpose. When we have two versions of a method (`foo` and `foo!`), the bang version should be more dangerous than the non-bang version. `dig!` is not the case.

And with whatever name, we need the real-world use-case for a new method. "We don't have `fetch` counterpart of `dig`" is not good enough.

Matz.


----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-76533

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91286] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (4 preceding siblings ...)
  2019-01-26 13:54 ` [ruby-core:91285] " matz
@ 2019-01-26 14:35 ` takashikkbn
  2019-01-28 17:06 ` [ruby-core:91308] " djo.went
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: takashikkbn @ 2019-01-26 14:35 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by k0kubun (Takashi Kokubun).


Personally I've hit real-world use-case for this feature many times.

I often manage structured configs with nested YAML files and load it from Ruby. With current ruby, to avoid unhelpful exception `NoMethodError`, I assert the existence of the deep keys using a `Hash#fetch` chain like this:

```ruby
config = YAML.load_file('config.yml')
config.fetch('production').fetch('environment').fetch('SECRET_KEY_BASE') #=> an exception like: KeyError: key not found: "SECRET_KEY_BASE"
```

If we have such a method, we would be able to write (let's say it's named `Hash#fetch_keys` instead of `#dig!`):

```ruby
config.fetch_keys('production', 'environment', 'SECRET_KEY_BASE')
```

and its good part is that we could get a more helpful error message like "key not found: production.environment.SECRET_KEY_BASE" whose nest information can't be get with `Hash#fetch` method chains.

---

By the way, if we had this, I would like to have a keyword argument `default:` like the second optional argument of `Hash#fetch`:

```ruby
env = 'production' # can be 'staging', 'development'
config.fetch_keys(env, 'environment', 'SECRET_KEY_BASE', default: '002bbfb0a35d0fd05b136ab6333dc459')
```

we want to safely manage the credentials only for production, so sometimes we don't want to manage credentials in (safely-managed originally-encrypted) YAML file for development environment and just want to return the unsafe thing as a default value.

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-76536

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91308] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (5 preceding siblings ...)
  2019-01-26 14:35 ` [ruby-core:91286] " takashikkbn
@ 2019-01-28 17:06 ` djo.went
  2019-03-12 21:56 ` [ruby-core:91795] " walerian.sobczak
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: djo.went @ 2019-01-28 17:06 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by 3limin4t0r (Johan Wentholt).


My scenario would be the similar as described by k0kubun.

```Ruby
# The connection translates the request to JSON and parses the response
# from JSON into the correct objects. In this case a nested hash structure.
response = connection.send(request)

# assign shortcuts
report = response
         .fetch('Era.Common.NetworkMessage.ConsoleApi.Reports.RpcGenerateReportResponse')
         .fetch('report')

column_data   = report.fetch('data').fetch('columns')
column_labels = report.fetch('rendering').fetch('table').fetch('columns')

# build report
report_data = column_data.each_with_object({}) do |column, data|
  column_id       = column.fetch('header').fetch('column_id')
  data[column_id] = column.fetch('values')
end

report = column_labels.each_with_object({}) do |column, data|
  label       = column.fetch('label').fetch('literal')
  column_id   = column.fetch('column_id')
  data[label] = report_data.fetch(column_id)
end
```

From the above scenario you can see that having this new functionality would help out a lot.

The reason I use `#fetch` here is because the API to which I'm talking might change it's structure. Getting an error as soon as possible reduces debug time, since the code won't move on with a `nil` value (that probably raises an exception somewhere later).

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-76554

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:91795] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (6 preceding siblings ...)
  2019-01-28 17:06 ` [ruby-core:91308] " djo.went
@ 2019-03-12 21:56 ` walerian.sobczak
  2019-06-13  8:08 ` [ruby-core:93096] " knu
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: walerian.sobczak @ 2019-03-12 21:56 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by walerian (Walerian Sobczak).


I would suggest `#retrieve`. It's just a **stronger** `#fetch`, and the dictionary definition reflects its meaning:
> retrieve (verb)
> 1. get or bring (something) back from somewhere
> 2. find or extract

The name is still short and simple, but also idiomatic and meaningful at the same time.

``` ruby
config = YAML.load_file('config.yml')

# so instead of this:
config.fetch('production').fetch('environment').fetch('SECRET_KEY_BASE')

# we would have:
config.retrieve('production', 'environment', 'SECRET_KEY_BASE')
```


----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-77069

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:93096] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (7 preceding siblings ...)
  2019-03-12 21:56 ` [ruby-core:91795] " walerian.sobczak
@ 2019-06-13  8:08 ` knu
  2019-11-18 13:08 ` [ruby-core:95876] [Ruby master " djo.went
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 12+ messages in thread
From: knu @ 2019-06-13  8:08 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by knu (Akinori MUSHA).


I thought adding an optional block to dig could be an idea.

```ruby
obj.dig(*keys) { |dug_keys, rest_keys|
  # raise yourself or return something
}
```

But the costs required for extending the dig method as such in Array or Hash-like classes would be a bit too high.

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-78518

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module DigWithException
  def dig_e(key, *others)
    value = fetch(key)
    return value if value.nil? || others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(DigWithException)
Hash.include(DigWithException)
```

The exception raised is also taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`). I personally have my issues with the name `#dig_e`, but I haven't found a better name yet.

There are also a few other things that I haven't thought out yet.

 1. Should this method be able to accept a block which, will be passed to the `#fetch` call and recursive `#dig_e` calls?  

    ```Ruby
    module DigWithException
      def dig_e(key, *others, &block)
        value = fetch(key, &block)
        return value if value.nil? || others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others, &block)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

 2. I currently kept the code compatible with the `#dig` description.

    > Extracts the nested value specified by the sequence of *key* objects by calling `dig` at each step, returning `nil` if any intermediate step is `nil`.

    However, with this new version of the method one could consider dropping the *"returning `nil` if any intermediate step is `nil`"* part, since this would be the more strict version.

    ```Ruby
    module DigWithException
      def dig_e(key, *others)
        value = fetch(key)
        return value if others.empty?

        if value.respond_to?(__method__, true)
          value.send(__method__, *others)
        else
          raise TypeError, "#{value.class} does not have ##{__method__} method"
        end
      end
    end

    Array.include(DigWithException)
    Hash.include(DigWithException)
    ```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:95876] [Ruby master Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (8 preceding siblings ...)
  2019-06-13  8:08 ` [ruby-core:93096] " knu
@ 2019-11-18 13:08 ` djo.went
  2019-11-18 13:16 ` [ruby-core:95877] " djo.went
  2019-12-01  8:43 ` [ruby-core:96044] " ariel.caplan
  11 siblings, 0 replies; 12+ messages in thread
From: djo.went @ 2019-11-18 13:08 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by 3limin4t0r (Johan Wentholt).

Description updated

Changed naming from `#dig_e` to `#traverse`. Removed alternative options, and kept the strict version since `#fetch` is also strict. Possibility to expand upon this with a block or `:default` keyword argument is possible, but should be its own feature request.

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-82713

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module Traversable
  def traverse(key, *others)
    value = fetch(key)
    return value if others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(Traversable)
Hash.include(Traversable)
```

The exception raised is taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`).

```Ruby
yaml = YAML.load_file('some_file.yml')

# this change is meant to make chaining #fetch calls more easy
yaml.fetch('foo').fetch('bar').fetch('baz')

# would be replaced with
yaml.traverse('foo', 'bar', 'baz')
```

I'm curious to hear what you guys think about the idea as a whole, the method name and the two points described above.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:95877] [Ruby master Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (9 preceding siblings ...)
  2019-11-18 13:08 ` [ruby-core:95876] [Ruby master " djo.went
@ 2019-11-18 13:16 ` djo.went
  2019-12-01  8:43 ` [ruby-core:96044] " ariel.caplan
  11 siblings, 0 replies; 12+ messages in thread
From: djo.went @ 2019-11-18 13:16 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by 3limin4t0r (Johan Wentholt).

Description updated

Removed the two discussion points in the previous edit. This edit adjusts the last sentence to reflect this.

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-82714

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module Traversable
  def traverse(key, *others)
    value = fetch(key)
    return value if others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(Traversable)
Hash.include(Traversable)
```

The exception raised is taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`).

```Ruby
yaml = YAML.load_file('some_file.yml')

# this change is meant to make chaining #fetch calls more easy
yaml.fetch('foo').fetch('bar').fetch('baz')

# would be replaced with
yaml.traverse('foo', 'bar', 'baz')
```

I'm curious to hear what you guys think about the idea as a whole and the method name.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [ruby-core:96044] [Ruby master Feature#15563] #dig that throws an exception if an key doesn't exist
       [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
                   ` (10 preceding siblings ...)
  2019-11-18 13:16 ` [ruby-core:95877] " djo.went
@ 2019-12-01  8:43 ` ariel.caplan
  11 siblings, 0 replies; 12+ messages in thread
From: ariel.caplan @ 2019-12-01  8:43 UTC (permalink / raw
  To: ruby-core

Issue #15563 has been updated by amcaplan (Ariel Caplan).


matz (Yukihiro Matsumoto) wrote:
> I am against `dig!` for this purpose. When we have two versions of a method (`foo` and `foo!`), the bang version should be more dangerous than the non-bang version. `dig!` is not the case.
> 
> And with whatever name, we need the real-world use-case for a new method. "We don't have `fetch` counterpart of `dig`" is not good enough.
> 
> Matz.

I think that I, along with others, have gotten used to bang methods being subject to more errors rather than mutating state, due to how ActiveSupport uses them. Ruby is not Rails, though, so this is a good reminder :)

I like `#deep_fetch`, which also happens to be a gem that already does this (https://rubygems.org/gems/deep_fetch). It was designed for implicitly validating that API formats have not changed, which says something about the need for this. I think having an easy way to `dig` while doing that constant validation would encourage writing code that notifies us right away when our assumptions are wrong.

As an alternative, maybe `#fetch_strict` or (though I like this less) a named parameter passed to `dig` like `my_array.dig(:foo, :bar, :baz, strict: true)`. The latter option currently isn't possible since it's unclear whether `strict: true` is a key or a named parameter, but that problem will go away in Ruby 3.

----------------------------------------
Feature #15563: #dig that throws an exception if an key doesn't exist
https://bugs.ruby-lang.org/issues/15563#change-82894

* Author: 3limin4t0r (Johan Wentholt)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Ruby 2.3.0 introduced `#dig` for *Array*, *Hash* and *Struct*. Both *Array* and *Hash* have `#fetch` which does the same as `#[]`, but instead of returning the default value an exception is raised (unless a second argument or block is given). *Hash* also has `#fetch_values` which does the same as `#values_at`, raising an exception if an key is missing. For `#dig` there is no such option.

My proposal is to add a method which does the same as `#dig`, but instead of using the `#[]` accessor it uses `#fetch`.

This method would look something like this:

```Ruby
module Traversable
  def traverse(key, *others)
    value = fetch(key)
    return value if others.empty?

    if value.respond_to?(__method__, true)
      value.send(__method__, *others)
    else
      raise TypeError, "#{value.class} does not have ##{__method__} method"
    end
  end
end

Array.include(Traversable)
Hash.include(Traversable)
```

The exception raised is taken from `#dig` (`[1].dig(0, 1) #=> TypeError: Integer does not have #dig method`).

```Ruby
yaml = YAML.load_file('some_file.yml')

# this change is meant to make chaining #fetch calls more easy
yaml.fetch('foo').fetch('bar').fetch('baz')

# would be replaced with
yaml.traverse('foo', 'bar', 'baz')
```

I'm curious to hear what you guys think about the idea as a whole and the method name.
 



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2019-12-01  8:43 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <redmine.issue-15563.20190125170732@ruby-lang.org>
2019-01-25 17:07 ` [ruby-core:91265] [Ruby trunk Feature#15563] #dig that throws an exception if an key doesn't exist djo.went
2019-01-25 17:48 ` [ruby-core:91266] " djo.went
2019-01-25 19:35 ` [ruby-core:91267] " shevegen
2019-01-26 13:14 ` [ruby-core:91284] " Ruby-Lang
2019-01-26 13:54 ` [ruby-core:91285] " matz
2019-01-26 14:35 ` [ruby-core:91286] " takashikkbn
2019-01-28 17:06 ` [ruby-core:91308] " djo.went
2019-03-12 21:56 ` [ruby-core:91795] " walerian.sobczak
2019-06-13  8:08 ` [ruby-core:93096] " knu
2019-11-18 13:08 ` [ruby-core:95876] [Ruby master " djo.went
2019-11-18 13:16 ` [ruby-core:95877] " djo.went
2019-12-01  8:43 ` [ruby-core:96044] " ariel.caplan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).