[ruby-core:122345] [Ruby Feature#21386] Introduce `Enumerable#join_map`

Issue #21386 has been reported by matheusrich (Matheus Richard). ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by prateekkish@gmail.com (Prateek Choudhary). PR: https://github.com/ruby/ruby/pull/13792 ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113933 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by nobu (Nobuyoshi Nakada). prateekkish@gmail.com (Prateek Choudhary) wrote in #note-2:
This difference is intentional? ```ruby [1,2,3].map {|n|[n]}.join(",") #=> "1,2,3" [1,2,3].join_map(",") {|n|[n]} #=> "[1],[2],[3]" ``` ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113934 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by nobu (Nobuyoshi Nakada). This code would show the difference more clearly. ```ruby [[1,2],3].map {|n|[n]}.join("|") #=> "1|2|3" [[1,2],3].join_map("|") {|n|[n]} #=> "[[1, 2]]|[3]" ``` ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113935 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by matheusrich (Matheus Richard). My expectation is that join_map would behave like map + join ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113936 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by prateekkish@gmail.com (Prateek Choudhary). nobu (Nobuyoshi Nakada) wrote in #note-4:
This code would show the difference more clearly.
```ruby [[1,2],3].map {|n|[n]}.join("|") #=> "1|2|3" [[1,2],3].join_map("|") {|n|[n]} #=> "[[1, 2]]|[3]" ```
Hmm. I missed considering that behavior. In this example though, just the `map` would return wrapped arrays: ```ruby [[1,2],3].map {|n|[n]} #=> [[[1, 2]], [3]] ``` So somehow doing a map and join is doing more of a flat_map + join. I can correct the PR to mimic that behavior. ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113937 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by Dan0042 (Daniel DeLorme). I am against this. ergonomics: Adding a special "X_Y" method for every common pattern of "X followed by Y" is truly horrible for the ergonomics of the language. It only multiplies the number of useless details the programmer should remember, without adding any expressiveness. Just because "map + join" is a common pattern doesn't mean it benefits from being expressed as a single method. performance: This appears intended to improve performance, but is there any benchmark? Is this really a measurable cost in any program, anywhere? It seems to me like a good JIT with escape analysis might already handle this for you, without having to remember the "special method for performance". Sorry but it looks entirely like premature micro-optimization to me. ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113976 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by mame (Yusuke Endoh). https://github.com/ruby/dev-meeting-log/blob/master/2019/DevMeeting-2019-03-...
[Feature #15323] Proposal: Add Enumerable#filter_map ... mame: if this method is accepted, other methods with map will be also requested. matz: filter_map is not simply combination of filter and map. so mame’s concern is needless fears. matz: ok, accepted `filter_map`.
That's why I was against it 🤦 ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113978 * Author: matheusrich (Matheus Richard) * Status: Open ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by matz (Yukihiro Matsumoto). Status changed from Open to Rejected I reject this proposal. Simply combine join and map at the moment. I hope JIT inlining will remove intermediate objects in the future. Matz. ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113986 * Author: matheusrich (Matheus Richard) * Status: Rejected ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by knu (Akinori MUSHA). FWIW, this function is called `mapconcat` in Emacs: https://www.gnu.org/software/emacs/manual/html_node/elisp/Mapping-Functions.... I once wanted this when I implemented shelljoin() and chose to just call map and join. I convinced myself that you shouldn't worry too much about performance. https://github.com/ruby/ruby/blob/65a0f46880ecb13994d3011b7a95ecbc5c61c5a0/l... ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-113989 * Author: matheusrich (Matheus Richard) * Status: Rejected ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by Eregon (Benoit Daloze). matz (Yukihiro Matsumoto) wrote in #note-9:
I hope JIT inlining will remove intermediate objects in the future.
FWIW, that's not really feasible, at least for cases where the input array has variable size. Unless both Array#map and Array#join are intrinsified and handled as a compound operation of sort, but that's not really reasonable as Array#join is far from trivial. That said, I think the cost of the `map` is only a small fraction of the cost of the `join`, and so not worth having a new core method for this. ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-114064 * Author: matheusrich (Matheus Richard) * Status: Rejected ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/

Issue #21386 has been updated by zverok (Victor Shepelev). Just a thought: shouldn't we add `#join` to `Enumerator::Lazy`? It wouldn't solve "logical repetitiveness" of the pattern, but might be a good _and_ idiomatic way to optimize the pattern when necessary. (Lazy enumerators are underused in the community, as I understand. And I personally would like to see them more popular.) ---------------------------------------- Feature #21386: Introduce `Enumerable#join_map` https://bugs.ruby-lang.org/issues/21386#change-114075 * Author: matheusrich (Matheus Richard) * Status: Rejected ---------------------------------------- ### Problem The pattern `.map { ... }.join(sep)` is extremely common in Ruby codebases: ```ruby users.map(&:name).join(", ") ``` It’s expressive but repetitive (both logically and computationally). This pattern allocates an intermediate array and does two passes over the collection. Real-world usage is widespread: - [Open source Ruby projects using this pattern](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F&type=code) - [Within Rails](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Arails&type=code) - [Within Ruby itself](https://github.com/search?q=lang%3Aruby+%2F%5C.map%5Cs*%5C%7B%5B%5E%7D%5D*%5C%7D%5C.join%2F+org%3Aruby&type=code) ### Proposal Just like `filter_map` exists to collapse a common `map + compact`, this proposal introduces `Enumerable#join_map`, which maps and joins in a single pass. ```ruby users.join_map(", ", &:name) ``` A Ruby implementation could look like this: ```ruby module Enumerable def join_map(sep = "") return "" unless block_given? str = +"" first = true each do |item| str << sep unless first str << yield(item).to_s first = false end str end end ``` The name `join_map` follows the precedent of `filter_map`, emphasizing the final operation (`join`) over the intermediate (`map`). ### Prior Art Some other languages have similar functionality, but with different names or implementations: #### Elixir Elixir has this via [the `Enum.map_join/3` function](https://hexdocs.pm/elixir/1.12/Enum.html#map_join/3): ```elixir Enum.map_join([1, 2, 3], &(&1 * 2)) "246" Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) "2 = 4 = 6" ``` #### Crystal Crystal, on the other hand, [uses `Enumerable#join` with a block](https://crystal-lang.org/api/1.16.3/Enumerable.html#join%28separator%3D%22%2...): ```crystal [1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3" ``` #### Kotlin Kotlin has a similar [function called `joinToString`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/join-to-str...) that can take a transformation function: ```kotlin val chars = charArrayOf('a', 'b', 'c') println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C ``` -- https://bugs.ruby-lang.org/
participants (9)
-
Dan0042 (Daniel DeLorme)
-
Eregon (Benoit Daloze)
-
knu (Akinori MUSHA)
-
mame (Yusuke Endoh)
-
matheusrich (Matheus Richard)
-
matz (Yukihiro Matsumoto)
-
nobu (Nobuyoshi Nakada)
-
prateekkish@gmail.com (Prateek Choudhary)
-
zverok (Victor Shepelev)