Question

I often find myself writing ruby code that looks like this:

b = f1(a)
c = f2(b)
d = f3(c)
...

Is there a way to chain f1, f2, f3 together regardless of the types of a, b, and c? For examplesomething like

a.send_to(&:f1)
  .sent_to(&:f2)
  .sent_to(&:f3)

It doesn't need to be exactly that, but I like the code that can be read, in order, as "start with a, now apply f1, then apply f2, ..."

It occurs to me that all I'm really asking for here is a way to use map on a single object rather than a list. E.g. I could do this:

[a].map(&:f1)
  .map(&:f2)
  .map(&:f3)
  .first

The following (sort of) works, though monkey-patching Object seems like a bad idea:

class Object
  def send_to(&block)
    block.call(self)
  end  
end  

I say "(sort of) works" because

1.send_to{|x| x+1}
#=> 2

but

def add_1(x); x+1; end  
1.send_to(&:add_1)
#=> NoMethodError: private method `add_1' called for 0:Fixnum

Note: This question asks something similar, but the proposal is to define a method on the particular class of each object.

Was it helpful?

Solution 4

Ruby 2.5's .yield_self does exactly this! https://zverok.github.io/blog/2018-01-24-yield_self.html

And if I follow this discussion correctly, .then is now a please alias for .yield_self https://www.reddit.com/r/ruby/comments/8nfqmt/kernelthen_is_now_an_alias_for_kernelyield_self/

OTHER TIPS

You can do as :

d = [:f1, :f2, :f3].inject(a) { |res,f| send(f, res) }

I think basically what you want is a combination of inject and send. Something like:

d = [:f1, :f2, :f3].inject(a) {|memo, fun| send(fun, memo) }

If you feel the necessity of doing that frequently, then your API design is wrong as a Ruby code. Rather than defining methods in function style:

b = f1(a)
c = f2(b)
d = f3(c)
...

define them in OOP style:

class A
  def f1; ... end # => b instance
end
class B
  def f2; ... end # => c instance
end
class C
  def f3; ... end # => d instance
end

Here, A is the class that a belongs to, B is the class that b belongs to, and so on (A, B, ... do not have to be all different. They can be Object or Kernel if you want to allow any object). Then, you can simply do chaining as:

a.f1.f2.f3. ...

If you start with f3 function you don’t need to change anything.
Secondly, ability to omit parenthesis is great.

For example:

a = 42

def f1 n; n end

def f2 n; n end

def f3 n; n end

f3 f2 f1 a

# => 42

With more complex functions you may add parenthesis.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top