From: "joel@... (Joel Drapper) via ruby-core" Date: 2023-02-11T11:16:24+00:00 Subject: [ruby-core:112365] [Ruby master Feature#19432] Introduce a wrapping operator (&) to Proc Issue #19432 has been updated by joel@drapper.me (Joel Drapper). Yes, I included an example in the original description. If we need to wrap a Proc in another Proc, there doesn't seem to be a clean way to do that. My first thought was to use this pattern: ```ruby when "text" result = -> { text node["text"] } node["marks"].each do |mark| case mark["type"] when "bold" result = -> { strong { result.call } } when "italic" result = -> { em { result.call } } end end result.call end ``` However, this raises a stack level to deep error. We have the `<<` and `>>` operators to compose Procs such that their return value is the input of the next Proc. We also have currying to transform a Proc with multiple arguments into a sequence of single-argument Procs. What we don't have is the ability to compose two or more Procs where one Proc wraps the other Proc. Here���s a simple example. Given these Proc: ```ruby strong = -> (&content) { "" << content.call << "" } em = -> (&content) { "" << content.call << "" } content = -> { "Hello World" } ``` We can explicitly compose them like this ```ruby strong.call { em.call { content.call } } ``` But if we started with the `content` and wanted to layer on `strong` and `em` conditionally, there isn't a clean way to do that. The `&` operator would allow for compositions like this ``` (content & strong & em).call ``` Or processes that transform a result iteratively. ```ruby result = content result &= strong if bold? result &= em if italic? result.call ``` Correcting my original post, I think it would be better if `&` coerced `self` into a block when calling the other Proc, since then it could be used to compose methods that accept blocks, e.g. ```ruby define_method :foo, method(:bar) & method(:baz) ``` The corrected implementation would be ```ruby class Proc def &(other) -> { other.call(&self) } end end ``` ---------------------------------------- Feature #19432: Introduce a wrapping operator (&) to Proc https://bugs.ruby-lang.org/issues/19432#change-101809 * Author: joel@drapper.me (Joel Drapper) * Status: Open * Priority: Normal ---------------------------------------- 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. ```ruby 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. ```ruby 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: ```ruby 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 ``` -- 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/postorius/lists/ruby-core.ml.ruby-lang.org/