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