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/