I'm having trouble understanding this code below.
I get the idea of Unary Ampersand Operator and passing procs as arguments to methods. But I really can't wrap my head around passing self
to the language.call
. I understand it like this: we're passing self
as an argument to the proc/block language. It doesn't make any sense to me. Can someone please explain? :)
class Translator
def speak &language
language.call(self)
end
protected
def french
'bon jour'
end
def spanish
'hola'
end
def turkey
'gobble'
end
def method_missing(*args)
'awkward silence'
end
end
We're using it with:
translator.speak(&:spanish)
This example beautifully ties together multiple Ruby concepts. Because of that, I will try to explain all of them.
Methods in Ruby can accept blocks (pieces of code) in elegant matter:
def run_code
yield
end
run_code { puts 42 } # => prints 42
deep_thought = proc { puts 42 }
deep_thought.call # => prints 42
&
operator:def run_code
yield
end
deep_thought = proc { puts 42 }
run_code(&deep_thought) # => prints 42
def reveal_answer
yield 5_000
end
deep_thought = proc do |years_elapsed|
years_elapsed >= 7_500_000 ? 42 : 'Still processing'
end
reveal_answer(&deep_thought) # => 'Still processing'
&
in the method signature:def inspector(&block)
puts block.is_a?(Proc)
puts block.call
end
inspector { puts 42 } # => prints true and 42
inspector(&proc { puts 42 }) # => the same
Symbol#to_proc
creates a proc that calls methods with the same name on the object:class Dummy
def talk
'wooooot'
end
end
:talk.to_proc.call(Dummy.new) # => "wooooot"
In other words,
:bar.to_proc.call(foo)
is pretty much equivalent to
foo.bar
BasicObject#method_missing
:When you try to call a method on an object, Ruby traverses it's ancestor chain, searching for a method with that name. How the chain is constructed is a different topic, lengthy enough for another day, the important thing is that if the method is not found til the very bottom (BasicObject
), a second search is performed on the same chain - this time for a method called method_missing
. It gets passed as arguments the name of the original method plus any argument it received:
class MindlessParrot
def method_missing(method_name, *args)
"You caldt #{method_name} with #{args} on me, argh!"
end
end
MindlessParrot.new.foo # => "You caldt foo with [] on me, argh!"
MindlessParrot.new.bar :baz, 42 # => "You caldt bar with [:baz, 42] on me, argh!"
So what does all this mean in our specific case? Lets assume for a second there was no protected
.
translator.speak(&:spanish)
calls the method Translator#speak
with :spanish
converted to block.
Translator#speak
takes that block and transforms it to a proc, named language
, and calls it, passing self
as argument.
self
is an instance of Translator
, therefore, it has the methods speak
, french
, spanish
, turkey
and method_missing
.
And so:
Translator.new.speak(&:spanish)
is equivalent to:
:spanish.to_proc.call(Translator.new)
which is equivalent to:
Translator.new.spanish
giving us "hola"
.
Now, taking the protected
back, all the language methods of our translator
object are still present, but they can not be directly accessed by outsiders.
Just as you can't call
Translator.new.spanish
and expect "hola"
back, you can't call
Translator.new.speak(&:spanish)
And since the method is not directly accessible, it is considered not found and method_missing
is called, thus giving us "awkward silence"
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With