Ruby supports two forms of methods:
- Bound Methods: These methods are associated to an object.
- Unbound Methods: These methods are not associated to any particular object.
Let’s say we have a User class.
# user.rb
class User
def initialize(name)
@name = name
end
def name
@name
end
def welcome
"Hi, #{self.name}"
end
end> user = User.new('John Doe')
# Bound Method
> user.method(:welcome)
#=> #<Method: User#welcome (SOME_PATH/user.rb):6>
# Unbound Method
> User.instance_method(:welcome)
#=> #<UnboundMethod: User#welcome (SOME_PATH/user.rb):6>We can create UnboundMethod using Module#instance_method or Method#unbind
and can call after binding to an object.
What is binding?
A Binding
object contains the execution context in the code.
The execution context consists of variables, methods, the value of self and block.
This context can be later accessed using binding function.
For example:
# person.rb
class Person
def initialize(name)
@name = name
end
def get_binding
binding
end
endNow try to access name from the instance of Person class.
> person = Person.new('John Doe')
#<Person:0x00007fac42439fe0 @name="John Doe">
> person.name
#=> NoMethodError (undefined method 'name' for #<Person:0x00007fac42439fe0 @name="John Doe">)We can access the name instance variable using eval.
This method takes the code as the first argument and binding as the second argument.
> eval('@name', person.get_binding)
#=> "John Doe"
> person.get_binding.receiver
#<Person:0x00007fac42439fe0 @name="John Doe">We use Method#bind to bind an object to UnboundMethod.
But the allocation from bind is quite expensive.
That is why Ruby 2.7 has added
UnboundMethod#bind_call to avoid the intermediate allocation.
Let’s say we have a Manager class which is inherited from User class.
class Manager < User
def welcome
"Hi Manager, #{self.name}"
end
endTo call UnboundMethod:
Before Ruby 2.7
> manager = Manager.new('John Doe')
# Calling an unbound method
> User.instance_method(:welcome).bind(manager).call
#=> "Hi, John Doe"Ruby 2.7
> manager = Manager.new('John Doe')
# Calling an unbound method
> User.instance_method(:welcome).bind_call(manager)
#=> "Hi, John Doe"Apart from object, we can also pass parameters to bind_call similar to call method.
class User
def welcome(name)
"Hi, #{name}"
end
end
> user = User.new
> User.instance_method(:welcome).bind_call(user, 'John Doe')
#=> "Hi, John Doe"Following are benchmark results:
# Student class
class Student
def score
end
end
> student = Student.new
> N = 100000
> Benchmark.bmbm do |x|
> x.report("bind.call") { N.times { Student.instance_method(:score).bind(student).call }}
> x.report("bind_call") { N.times { Student.instance_method(:score).bind_call(student) }}
> end
#
# Rehearsal ---------------------------------------------------
# bind.call 0.066435 0.000766 0.067201 ( 0.067263)
# bind_call 0.042495 0.000049 0.042544 ( 0.042587)
# ------------------------------------------ total: 0.109745sec
#
# user system total real
# bind.call 0.063620 0.000201 0.063821 ( 0.063870)
# bind_call 0.040765 0.000025 0.040790 ( 0.040812)As we can see that bind_call is a bit faster than bind.call.
