Unary Ampersand operator and passing procs as arguments in Ruby

I find it hard to understand this code below.

I get the idea of ​​a Unary Ampersand Operator and pass procs as arguments to the methods. But I really can't wrap my head going self to language.call . As I understand it: we pass self as an argument for the proc / block language. It makes no sense to me. Can someone 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 use it with:

 translator.speak(&:spanish) 
+5
source share
1 answer

This example perfectly links several Ruby concepts. Because of this, I will try to explain them all.

  • Blocks

Methods in Ruby can take blocks (code snippets) in elegant material:

 def run_code yield end run_code { puts 42 } # => prints 42 
  1. Procs are similar to blocks, but they are relevant addressable objects:
 deep_thought = proc { puts 42 } deep_thought.call # => prints 42 
  1. You can include proc in a block when calling a method with the & operator:
 def run_code yield end deep_thought = proc { puts 42 } run_code(&deep_thought) # => prints 42 
  1. Procs and blocks can take arguments:
 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' 
  1. You can turn the block into proc using & 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 
  1. 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) 

pretty much equivalent

 foo.bar 
  1. BasicObject#method_missing :

When you try to call a method on an object, Ruby traverses the chain of ancestors looking for a method with that name. How the chain is built is another topic, long enough for another day, it is important that if the method is not found to the very bottom ( BasicObject ), the second search is performed in the same chain - this time for a method called method_missing . It takes as arguments the name of the original method plus any argument it receives:

 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 particular case? Suppose there was no protected for a second.


 translator.speak(&:spanish) 

calls the Translator#speak method with :spanish converted to a block.


Translator#speak takes this block and converts it to proc with the name language and calls it, passing self as an argument.


self is an instance of Translator , so it has the methods speak , french , spanish , turkey and method_missing .


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, returning protected back, all the language methods of our Translator object are still present, but outsiders cannot directly access them.


Just like you can't call

 Translator.new.spanish 

and expect a "hola" back, you cannot call

 Translator.new.speak(&:spanish) 




And since the method is not directly accessible, it is considered not found and method_missing is method_missing , which gives us "awkward silence" .

+7
source

All Articles