Project

General

Profile

Feature #15831

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

Added by bogdanvlviv (Bogdan Denkovych) 10 months ago. Updated 9 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

Updated by shevegen (Robert A. Heiler) 10 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) 10 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) 10 months ago

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

Updated by bogdanvlviv (Bogdan Denkovych) 10 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) 10 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) 10 months ago

  • Description updated (diff)
#7

Updated by bogdanvlviv (Bogdan Denkovych) 10 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) 9 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) 9 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) 9 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