Feature #21386
openIntroduce `Enumerable#join_map`
Description
Problem¶
The pattern .map { ... }.join(sep)
is extremely common in Ruby codebases:
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:
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.
users.join_map(", ", &:name)
A Ruby implementation could look like this:
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:
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:
[1, 2, 3].join(", ") { |i| -i } # => "-1, -2, -3"
Kotlin¶
Kotlin has a similar function called joinToString
that can take a transformation function:
val chars = charArrayOf('a', 'b', 'c')
println(chars.joinToString() { it.uppercaseChar().toString() }) // A, B, C
Updated by mame (Yusuke Endoh) 8 days ago
- Related to Feature #21455: Add a block argument to Array#join added
Updated by [email protected] (Prateek Choudhary) 1 day ago
Updated by nobu (Nobuyoshi Nakada) 1 day ago
[email protected] (Prateek Choudhary) wrote in #note-2:
This difference is intentional?
[1,2,3].map {|n|[n]}.join(",") #=> "1,2,3"
[1,2,3].join_map(",") {|n|[n]} #=> "[1],[2],[3]"
Updated by nobu (Nobuyoshi Nakada) 1 day ago
This code would show the difference more clearly.
[[1,2],3].map {|n|[n]}.join("|") #=> "1|2|3"
[[1,2],3].join_map("|") {|n|[n]} #=> "[[1, 2]]|[3]"
Updated by matheusrich (Matheus Richard) about 19 hours ago
My expectation is that join_map would behave like map + join
Updated by [email protected] (Prateek Choudhary) about 15 hours ago
· Edited
nobu (Nobuyoshi Nakada) wrote in #note-4:
This code would show the difference more clearly.
[[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:
[[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.
EDIT: The PR has been updated.