From: "nobu (Nobuyoshi Nakada) via ruby-core" <ruby-core@...>
Date: 2024-11-16T04:50:36+00:00
Subject: [ruby-core:119945] [Ruby master Feature#20899] Reconsider adding `Array#find_map`

Issue #20899 has been updated by nobu (Nobuyoshi Nakada).


I use ���`break` from `find` block��� quite often, and admit such method will be useful.
As for this name, I���m not sure if it is appropriate, though.

----------------------------------------
Feature #20899: Reconsider adding `Array#find_map`
https://bugs.ruby-lang.org/issues/20899#change-110668

* Author: toy (Ivan Kuchin)
* Status: Open
----------------------------------------
I would like to retry proposing method `Array#find_map` that was rejected in [8421](https://bugs.ruby-lang.org/issues/8421) which happened before introduction of `filter_map` in [15323](https://bugs.ruby-lang.org/issues/15323).
It would make code nicer whenever there is a need to get the first truthy result of applying some code.

Adapting examples from `filter_map` documentation, but if I need only the first value:
```rb
(1..9).find_map {|i| i * 2 if i.even? }                              # => 4
{foo: 0, bar: 1, baz: 2}.find_map {|key, value| key if value.even? } # => :foo
```

Or an example of getting match group for first successful match:

```rb
list = ['some 123', 'list 234', 'of 345', 'strings 456']

list.find_map{ |s| s[/\Aof (\d+)\z/, 1] } # => "345"
```

Currently I imagine either more code and/or inefficiency (extra calls and/or objects):

```rb
# code called twice
list.find{ |s| s[/\Aof (\d+)\z/, 1] }&.then{ |s| s[/\Aof (\d+)\z/, 1] } # => "345"

# more logic
result = nil
list.each do |s|
  break if (result = s[/\Aof (\d+)\z/, 1])
end
result # => "345"

# or
result = nil
list.find do |s|
  result = s[/\Aof (\d+)\z/, 1]
end
result # => "345"

# extra calls for items which come after item that we were looking for
list.map{ |s| s[/\Aof (\d+)\z/, 1] }.find{ _1 } # => "345"

# using lazy
list.lazy.map{ |s| s[/\Aof (\d+)\z/, 1] }.find{ _1 } # => "345"

# or as suggested by @alexbarret in https://bugs.ruby-lang.org/issues/8421?tab=history#note-7
list.lazy.filter_map{ |s| s[/\Aof (\d+)\z/, 1] }.first # => "345"

# using tricks, as suggested by @zverok in https://bugs.ruby-lang.org/issues/8421?tab=history#note-4
list.find{ |s| result = s[/\Aof (\d+)\z/, 1] and break result } # => "345"
```

Implementation in ruby can be:
```rb
Enumerable.class_eval do
  def find_map(&block)
    each do |element|
      block_result = block.call(element)
      return block_result if block_result
    end
    
    nil
  end
end
```

An example from another language - scala method [`collect`](https://www.scala-lang.org/api/3.5.2/scala/collection/ArrayOps.html#collect-68e) works alike `filter_map` and [`collectFirst`](https://www.scala-lang.org/api/3.5.2/scala/collection/ArrayOps.html#collectFirst-25d) would be like `find_map`.




-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/