Project

General

Profile

Feature #15831

Add `Array#extract`, `Hash#extract`, and `ENV.extract`

Added by bogdanvlviv (Bogdan Denkovych) 6 months ago. Updated 6 months ago.

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

Description

Add Array#extract

The method removes and returns the elements for which the block returns a true value.
If no block is given, an Enumerator is returned instead.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]

This method was added to Active Support as extract! in https://github.com/rails/rails/pull/33137

In this post, you can find use cases of this method
https://bogdanvlviv.com/posts/ruby/rails/array-extract-to-activesupport-6-0.html

Add Hash#extract

The method removes and returns the key/value pairs for which the block evaluates to +true+.
If no block is given, an Enumerator is returned instead.

h = {a: 100, b: 200, c: 300}
h.extract {|k, v| v > 150} # => {:b=>200, :c=>300}
h # => {:a=>100}

Note that there is method extract! in Active Support that was added in 2009, see
https://github.com/rails/rails/commit/8dcf91ca113579646e95b0fd7a864dfb6512a53b
But I think we should upstream extract! to Ruby as slice!.

Add ENV.extract

The method removes and returns the key/value pairs for which the block evaluates to +true+.
If no block is given, an Enumerator is returned instead.

ENV.extract {|k, v| k == "PORT"} # => {"PORT"=>"3000"}

Pull Request: https://github.com/ruby/ruby/pull/2171
Patch: https://patch-diff.githubusercontent.com/raw/ruby/ruby/pull/2171.patch

History

Updated by shevegen (Robert A. Heiler) 6 months ago

What is the difference towards e. g. hash.delete()?

People may ask about this difference. The '!'
too since you can use e. g. Hash #delete as-is.

Also without '!', I am not sure if extract is a good
name per se, but these are just random comments mostly,
you only have to convince matz, not me. :)

#2

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

  • Description updated (diff)
  • Subject changed from Add `Array#extract!`, `Hash#extract!`, and `ENV::extract!` to Add `Array#extract`, `Hash#extract`, and `ENV::extract`

Updated by nobu (Nobuyoshi Nakada) 6 months ago

Why Array#extract has no argument but takes a block, while Hash and ENV are opposite?

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

nobu (Nobuyoshi Nakada) wrote:

Why Array#extract has no argument but takes a block, while Hash and ENV are opposite?

I implemented those methods to mirror similar behavior as it is in Active Support, but I also feel like we need to discuss more about behavior and signature of those methods:

About Array#extract

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]

I do believe that this method should mirror the behavior and the method signature of Array#reject! https://docs.ruby-lang.org/en/2.6.0/Array.html#method-i-reject-21 since they are similar except one thing - Array#extract returns elements for which the block returns a true value. Actually "return elements for the block returns a true value" is the main reason why I would like to add this method because there could be and there already are lots of use cases for those methods. So I believe this method good as it is.

About Hash#extract and ENV::extract

First of all, thank you for your question. I also feel Hash#extract/ENV::extract should receive a block, in other words should mirror the behavior and the method signature oHash#reject!, but return the hash with key/value pairs for which the block returns true. There is an example to make it clear:

h = {a: 100, b: 200, c: 300}
h.extract {|k, v| v > 150} # => {:b=>200, :c=>300}
h # => {:a=>100}

I also feel like we should add Hash#slice!(since we have Hash#slice) that would behave exactly in the way I proposed to Hash#extract initially.

h = {a: 100, b: 200, c: 300}
h.slice!(:a) # => {:a=>100}
h # => {:b=>200, :c=>300}
h.slice!(:b, :c, :d) # => {:b=>200, :c=>300}
h # => {}

I would like to work on Hash#slice! implementation as well.

Let me know what you think about it.

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

  • Description updated (diff)

I just changed the implementation of Hash#extract and ENV::extract as it's described in the previous note https://bugs.ruby-lang.org/issues/15831#note-4

#6

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

  • Description updated (diff)
#7

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

  • Description updated (diff)
  • Subject changed from Add `Array#extract`, `Hash#extract`, and `ENV::extract` to Add `Array#extract`, `Hash#extract`, and `ENV.extract`

Updated by matz (Yukihiro Matsumoto) 6 months ago

  • Status changed from Open to Rejected

I don't think we have seen the use-case that this method is absolutely necessary. I also concern about the name conflict with the ActiveSupport method.

Let me see the real-world use-case, please.

Matz.

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

matz (Yukihiro Matsumoto) wrote:

I don't think we have seen the use-case that this method is absolutely necessary. I also concern about the name conflict with the ActiveSupport method.

Let me see the real-world use-case, please.

Matz.

There are use-cases of Array#extract in this Pull Request https://github.com/rails/rails/pull/33137/files

There are also use-cases where Array#extract would fit in the ruby/ruby codebase:

1) https://github.com/ruby/ruby/blob/d0a54673202458455244f79ed212a97727f0c7c7/lib/rubygems/uninstaller.rb#L91-L93

- default_specs, list = list.partition do |spec|
-   spec.default_gem?
- end
+ default_specs = list.extract {|spec| spec.default_gem?}

2) https://github.com/ruby/ruby/blob/d48783bb0236db505fe1205d1d9822309de53a36/lib/rubygems/commands/cleanup_command.rb#L128-L130

- default_gems, gems_to_cleanup = gems_to_cleanup.partition do |spec|
-   spec.default_gem?
- end
+ default_gems = gems_to_cleanup.extract {|spec| spec.default_gem?}

3) https://github.com/ruby/ruby/blob/d48783bb0236db505fe1205d1d9822309de53a36/spec/mspec/lib/mspec/runner/context.rb#L185-L187

- filtered, @examples = @examples.partition do |ex|
-   ex.filtered?
- end
+ filtered = @examples.extract {|ex| ex.filtered? }

4) https://github.com/ruby/ruby/blob/d48783bb0236db505fe1205d1d9822309de53a36/lib/rubygems/dependency.rb#L331

- pre, matches = matches.partition { |spec| spec.version.prerelease? }
+ pre = matches.extract { |spec| spec.version.prerelease? }

5) https://github.com/ruby/ruby/blob/2e8b9aba9bef6c913e5b2de25b24026943438519/lib/bundler/resolver.rb#L118

- pre, results = results.partition {|spec| spec.version.prerelease? }
+ pre = results.extract {|spec| spec.version.prerelease? }

6) https://github.com/ruby/ruby/blob/d48783bb0236db505fe1205d1d9822309de53a36/test/lib/test/unit.rb#L507

- suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}}
+ suites = rep.extract {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}}

By the way, numpy/numpy library has similar extract method: https://www.numpy.org/devdocs/reference/generated/numpy.extract.html?highlight=extract

Updated by bogdanvlviv (Bogdan Denkovych) 6 months ago

I also concern about the name conflict with the ActiveSupport method.

I guess this comment addressed https://bugs.ruby-lang.org/issues/15863 that adds Hash#slice!, right?
Active Support doesn't have any extract methods, only extract!(with bang).

Also available in: Atom PDF