четверг, 13 января 2011 г.

Ruby и &

В Ruby можно встретить такие вот "элегантные" (элегантности хоть отбавляй) конструкции:



def contrived(a, &f)
    # блок (block) может быть доступен через f
    f.call(a)
    # но yield также работает!
    yield(a)
end
def some_method(&block) 
    block.class 
end


Объяснение этой элегантности можно найти в Унарный амперсанд

Рассмотрим пример:

User.all.map &:name           # получить массив имен пользователей

вместо


User.all.map { |user| user.name }

Сначала кажется что это свойство перечисляемых классов, но на самом деле это не так.

Магия #1.

Когда ruby встречает амперсанд (&) в последнем аргументе вызова метода,
то пытается превратить его в выполняемый блок кода (Proc). Например:

a = (1..10).to_a
a.map { |n| n*n }             # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
 
l = lambda { |n| n*n }
a.map &l                      # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Магия #2.

Ruby, встречая амперсанд, превращает обьект в выполняемый блок через вызов метода #to_proc.

И вот он главный сюрприз, вызывая #to_proc у Symbol мы получаем примерно следующий блок кода:

lambda { |x| x.send(self) }

То есть Symbol#to_proc возвращет именно тот блок, который мы от него ожидали, потому что он в таком виде уже определен в классе Symbol.


Пару слов про:


def some_method(&block) 
    block.class 
end
 
В этом примере знак & - это способ отметить аргумент, который будет соответствовать передаваемому блоку.
Для вызова блока нужно вызвать метод call у нашего блока c требуемыми параметрами (our_block.call(x))
 
Давайте узнаем, чем же на самом деле в последнем примере является наш блок (block).
 
puts some_method {}# => Proc


Вот такие чудеса.


Комментариев нет:

Отправить комментарий