3

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.

5
  • What do you mean by “chaining”? Do you need dots (.). Do you care about order: a.f1.f2, f2.f1.a. Does one of order preferable or are both ok? Commented Apr 23, 2014 at 17:24
  • 2
    Am I missing something? What's wrong with f3(f2(f1(a)))? Commented Apr 23, 2014 at 18:23
  • @meagar - f3(f2(f1(a)) will do in a pinch, but esp. when there are many more fN's, it's much easier for others to follow when the operations are in order, so it can be read as "start with a, then apply f1, then apply f2, ..." Commented Apr 23, 2014 at 18:34
  • @Darek Nędza - good point; just edited the question to (hopefully) clarify the desired properties. Commented Apr 23, 2014 at 18:38
  • @brahn I agree, that's why the way it's currently written in the question is already the best way. None of the upvoted solutions below should ever exist in a real codebase, they're adding a hugely opaque layer of WTF to a simple series of function invocations. Commented Apr 23, 2014 at 18:38

5 Answers 5

6

You can do as :

d = [:f1, :f2, :f3].inject(a) { |res,f| send(f, res) }
Sign up to request clarification or add additional context in comments.

1 Comment

@archie Of course it would. The better question is, does it matter?
4

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) }

Comments

3

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. ...

Comments

2

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/

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.