Meta-Programming Ruby

Ruby basic

===, =~, equal?, ==, eql?

The === is a more broad definition to ==. If the two objects to compare are String, Number, the === is equal to ==.
If the left value is regular expression, the === is equal to =~. The case / when is using the === to match values.

The equal? method used to compare whether two objects are equal. Actually, the implementation of it is to compare the two values’s object_id.

We use eql? to compare two object’s values. In most cases, the eal? is equal to ==, but the number is an exception. 1.0 == 1 is true, but 1.0.eql? 1 is false. When using a number as a hash key, we should notice 1.0 is not equal to 1, they are two different keys because the Hash use eql? to compare its keys.

Code Block & Proc

5 ways to create a callable object

  1. Proc.new
1
2
3
4
5
say = Proc.new do |msg|
puts msg
end

say = Proc.new { |msg| puts msg }
  1. proc
1
2
3
4
5
say = proc do |msg|
puts msg
end

say = proc { |msg| puts msg }
  1. lambda
1
2
3
4
5
say = lambda do |msg|
puts msg
end

say = lambada { |msg| puts msg }
  1. Operator ->
1
2
3
4
5
say = -> (msg) do
puts msg
end

say = -> (msg) { puts msg }
  1. Operator &
1
2
3
4
5
6
def execute(&say)
say # Proc
end

say = execute { |msg| puts msg }
say.call 'hello' # => 'hello'

The differences between lambda and Proc

  1. The behavior of return
  • The lambada returns like a function.
1
2
3
4
5
6
def execute(func)
value = func.call # Returns out of the func directly.
value * 2
end
f = proc { return 100 }
execute f # nil
  • The Proc returns from the scope declaring it.
1
2
3
4
5
6
def execute(func)
value = func.call # value = 100
value * 2
end
f = lambda { return 100 }
execute f # => 200
  1. Different responses to arity
1
2
3
4
5
func = lambda { |a, b| puts a, b }
func1 = proc { |a, b| puts a, b }

puts func.arity # => 2
puts func1.arity # => 2

The lambda likes a function. it can only receive the correct number of parameters. The Proc can automatically adapt more parameters or less

1
2
3
4
func.call 1, 2, 3 # Error

func1.call 1, 2, 3 # => 1, 2
func1.call 1 # => 1, nil

Method & Unbound method

The another callable object is the Method. We can extract a method form an object like this:

1
2
3
4
5
6
7
8
class Person {
def say
puts 'hello'
end
}

method = Person.new.method :say # => #<Method: Person#say>
method.call # => hello

The Method and Proc are similar. The only difference is theirs executing environment. Methods run in an object’s scope and Proc run where they are declared.

If we invoke instance_method to a class or module, we can get an unbound method.

1
func = Person.instance_method :say # => #<UnboundMethod: Person#say>

We cannot invoke the unbound method straightly. We need to convert the unbound method to a bound method via binding an object for it.

1
2
3
4
func = Person.instance_method :say # => #<UnboundMethod: Person#say>
me = Person.new
func = func.bind(me) # => #<Method: Person#say>
func.call # => hello

A class’s unbound methods can only bind the instances from its class or subclasses. If the unbound method from a module, it can bind any classes’s instances.

We can pass an unbound method to the define_method to implement dynamic binding.

1
2
3
4
5
6
7
8
9
module Echo
def echo
puts self
end
end

String.send :define_method, :say, Echo.instance_method(:echo)

"123".say # => 123

Any object has a meta class

Accessing an object’s meta class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person 

end

class << Person
self # The meta class of the class Person
def type
puts "Person"
end
end
# The meta class's instance method is the Person's class method.
Person.type # => Person


me = Person.new
# Accessing an object's meta class.
class << me
def run
puts 'running'
end
end
me.run # => running

When instance_eval is used, new methods are defined on the metaclass, but the self is the object itself.