Legends say that in Ruby everything is an object. You might say: “Ha, the method can’t be an object!” And you would be wrong because there is a Method object in Ruby. Of course, I am exaggerating. It is not hidden. In fact, it is very well known and documented. We’ve used Method object in one of our projects and I am writing this blog post to share some goodies that it provides.
Currying with #curry
There is a way to do function currying in Ruby with #curry
method. Let’s see how it works in action. Let’s say we have an object of class Greeter that accepts two arguments as an input. What if you would want to add an argument to this method, store this action as a function and then add the second argument later on? That’s where #curry
comes in. Let’s write some Ruby:
class Greeter def say_hello(greeting, name) puts "#{greeting} #{name}!" end end greeter = Greeter.new curryied_say_hello = greeter.method(:say_hello).curry say_hello_with_greeting = curryied_say_hello.call("Hello Sir") # Do some work say_hello_with_greeting.call("Janis") say_hello_with_greeting.call("Bob")
Let’s execute this code:
ruby curry.rb Hello Sir Janis! Hello Sir Bob!
What has happened here?
First of all, we’ve created a Method object by calling #method
method on our Greeter
instance and passed the name of the method, in our case – :say_hello
. This creates a Method object. We can store it and call it at any particular time with the arguments we require. Method object itself has a #curry
method, which turns our Method object into callable proc. When it is called for the first time, it is returning yet another proc, but with the first argument stored. In that way, we can store the first argument that is passed to this method and then executes this method with the second argument.
Unbinding a method
Now, here we are getting to the “dark side” of the Internet.
First of all, let’s create two classes, Monkey
and Human
, that both inherit from a single class – Creature
; create Method object from Creature
instance and bind it to Monkey
and Human
instance. The method that we will torture will be #use_tool
.
class Creature def use_tool puts "Use tool as #{self.class.to_s}" end end class Monkey < Creature end class Human < Creature end creature = Creature.new monkey = Monkey.new human = Human.new use_tool = creature.method(:use_tool) use_tool_unbound = use_tool.unbind monkey_bound_use_tool = use_tool_unbound.bind(monkey) monkey_bound_use_tool.call human_bound_use_tool = use_tool_unbound.bind(human) human_bound_use_tool.call
As you can see, we unbound a method from “creature” and bound it to “human” and “monkey”. So, how is this different from a plain call(:method_name)
? The idea behind Method object, that it contains all the computation for the method. If we would have changed the method dynamically in the class itself, and then have called Method object, it would have returned the computation from a Method object and not a newly defined method. Note that object that you want to bind this method to has to be #kind_of?
the original object.
Where is this useful? I am not really sure. At the moment, I can’t figure out a solid example of using this and the information on the Internet is pretty scarce.
Parameters of a method
Do you want to inspect the parameters of a method? No problem – Only 4.99$ + In-app purchases.
It is quite simple – lets analyze a case:
class Mailer def send_email(email, user = NullUser.new, body:, subject: "Default subject") puts "#{email} #{subject} #{body}!" end end
Let’s explore how “say hello” looks in terms of the interface:
irb(main):008:0> Mailer.new.method(:send_email).parameters => [[:req, :email], [:opt, :user], [:keyreq, :body], [:key, :subject]]
The great thing is that we can get all the required arguments, optional arguments, required keyword arguments and optional keyword arguments. We’ve used this method in order to understand if we can call a remote operation on an object before even calling it. I will go into further details in my next blog post.
Conclusions
Ruby is awesome! There are so many cool things hidden under the hood that you can experiment and play with. And all these features make Ruby the way we know it to be. I encourage you to explore standard lib and experiment.