DEV Community

Adrian Lee
Adrian Lee

Posted on

Ruby: Call me (in which an old dog learns new tricks)

A few years ago I worked somewhere where the lead dev wanted us to adhere to SRP and expose the class and instance via a single interface named call e.g:

def Foo
  def self.call = new.call
  def call = "hello"
end
Enter fullscreen mode Exit fullscreen mode

This actually ended up making a pretty clean codebase in certain respects (certainly helped ease the problem of naming things) although ignoring more obvious conventions such as to_h when returning a hash, to_s for strings etc eventually left me feeling it was too rigorously applied.

That aside, one of the missed opportunities was in generating a broader understanding of why a class/instance like this is useful and I recently had an opportunity to revisit the question.

In case you aren't aware, there are couple of ruby objects which already respond to call namely lambda and proc.

l = lambda { |x| x * x }
puts l.call(2) # 4

p = proc { |x| x + x }
puts p.call(3) # 6
Enter fullscreen mode Exit fullscreen mode

You can also covert a method into a proc using method or (preferably) public_method:

def foo(x) = x - x
m = method(:foo)
puts m.call(10) # 0 
Enter fullscreen mode Exit fullscreen mode

With this in mind you can pass around a lambda, a proc, a method, a class, or an instance of a class, all executed with the same interface. This is very useful for callbacks and differed execution.

As for tricks ... there are several ways to actually call call. l.call(2) is obvious but you can also do l.(2).

For procs, lambdas, and methods, there is also l[2] and l.yield(2) (I didn't know this!) but in the case of a class or instance, you'll have to add support (e.g. alias_method :[], :call) which starts to make the class a little messy, so I tend to stick with .().

But there's more. if you're a fan of Elixirs pipes:

l = lambda { |x| x * x }
p = proc { |x| x + x }
output = proc { |x| puts "*** #{x} ***" }

(l >> p >> output).call(2) # *** 8 ***
(output << p << l).call(2) # *** 8 ***
Enter fullscreen mode Exit fullscreen mode

I don't know that I'd approve code like this in a PR unless there was a really good reason but I had no idea this was possible. New tricks!

Top comments (0)