Helper for method pipelines?


#1

Here is a minor coding problem that I suspect is common enough to have
an idiomatic solution. I just don’t know what that solution is. If
you know of a more Rubyish way, please let me know.

Here’s the problem.

Say you have a starting value and want to run it through a method
pipeline to yield a final result. Part way through the pipeline, you
end up with an intermediate result that you cannot just chain into the
pipeline – it doesn’t have the necessary method. So you are forced
to save that result into a variable and then pass it into some other
object’s method in order to continue the pipeline and yield the final
result:

 intermediate_result =
   value.
   method1(args).
   method2(args)

 final_result =
   some_other_object.method3(intermediate_result)

What I do is add a ‘with_self’ method to Object that lets me incorporate
foreign method calls into my pipeline:

 final_result =
   value.
   method1(args).
   method2(args).
   with_self { |x| some_other_object.method3(x) }

The definition is straightforward:

 class Object
   def with_self
     block_given? ? yield(self) : self
   end
   def in_passing
     yield(self)
     self
   end
 end

I also define an ‘in_passing’ method for those times when you want to
preserve the input value yet process it for the side effects:

 1.with_self { |x| x + 1 }
 # returns 2

 1.in_passing { |x| p(x + 1) }
 # prints 2
 # returns 1

The thing is, I can’t believe that something like this isn’t already
built into Ruby. I’m I overlooking it?

Thanks for your help.

Cheers,
Tom


#2

Any particular reason you just can’t write it like

some_other_object.method3(value.method1(args).method2(args))

Farrel


#3

Farrel L. wrote:

Any particular reason you just can’t write it like

some_other_object.method3(value.method1(args).method2(args))

Yes, because it obscures the flow of the data, especially in more
complex examples.

In the general case, where you may have several intermediate values, and
where the methods and arguments may not be so conveniently compact, such
a refactoring seems likely to be hard to understand. What was a
top-to-bottom pipeline has become a zigzag flow of data.

Consider this example (sans comments):

def crumbs_from_path_parts(parts)
parts.map do |crumb|
crumb.
gsub(//index/,’’).
sub(/.*–/,’’).
gsub(/ANY|-/,’ ‘).
sub(/^articles//,’’).
strip.
split(/\s+/).
map { |s| @part_namer.canonicalize(s) }.
join(" ").
with_self { |s| @breadcrumb_namer.canonicalize(s) }
end
end

You can easily visualize the data flow, top to bottom, as it passes
through each transformative step.

Now let’s say I want to trace the value around the last step. I would
do it like this, without disturbing the flow of the pipeline:

def crumbs_from_path_parts(parts)
parts.map do |crumb|
crumb.
gsub(//index/,’’).
sub(/.*–/,’’).
gsub(/ANY|-/,’ ‘).
sub(/^articles//,’’).
strip.
split(/\s+/).
map { |s| @part_namer.canonicalize(s) }.
join(" ").
in_passing { |s| @logger.debug “before naming: #{s}” }.
with_self { |s| @breadcrumb_namer.canonicalize(s) }.
in_passing { |s| @logger.debug “after naming: #{s}” }
end
end

For cases like this, I think the pipeline is the simplest
representation. Rewriting to remove in_passing and with_self would
increase the code’s complexity.

What I want to know is whether Ruby has anything like in_passing and
with_self built in. Given how common pipelining is, I would expect so,
but I can’t find them.

Cheers,
Tom