Feature #19432
closedIntroduce a wrapping operator (&) to Proc
Description
I don't know if this concept exists under another name, or whether there’s a technical term for it. I often find myself wanting to wrap a proc in another proc.
Here's a snippet from a recent example where a visitor class renders a TipTap AST. Given a text node, we want to output the text by calling the text method with the value. But if the text has marks, we want to iterate through each mark, wrapping the output for each mark. It's possible to do this using the >>= operator if each proc explicitly returns another proc.
when "text"
result = -> { -> { text node["text"] } }
node["marks"]&.each do |mark|
case mark["type"]
when "bold"
result >>= -> (r) { -> { strong { r.call } } }
when "italic"
result >>= -> (r) { -> { em { r.call } } }
end
end
result.call.call
end
This is quite difficult to follow and the result.call.call feels wrong. I think the concept of wrapping one proc in another proc would make for a great addition to Proc itself. I prototyped this using the & operator.
class Proc
def &(other)
-> { other.call(self) }
end
end
With this definition, we can call & on the original proc with our other proc to return a new proc that calls the other proc with the original proc as an argument. It also works with &=, so the above code can be refactored to this:
when "text"
result = -> { text node["text"] }
node["marks"]&.each do |mark|
case mark["type"]
when "bold"
result &= -> (r) { strong { r.call } }
when "italic"
result &= -> (r) { em { r.call } }
end
end
result.call
end