Унарный амперсанд

Ruby*
Расскажу как в Ruby работает такая элегантная конструкция:

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.

UPD. Примерчик


Такое свойство Symbol дает удивительную возможность вызывать методы, подставляя объекты в качестве параметров:

:upcase.to_proc.call "asdad"     # => "ASDAD"


Материалы по теме


blog.hasmanythrough.com/2006/3/7/symbol-to-proc-shorthand
weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html
en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls#The_ampersand_.28.26.29
m.onkey.org/let-s-start-with-wtf
+54
12 января 2011, 14:23
38
dapi 22,0

комментарии (17)

+7
n0ne #
Не жадничайте, пишите чаще про такие штуки (-:
0
dapi #
А вы спрашивайте если что-то интересно, в личку или в комментах. А я буду в статьях объяснять.
0
GreyCat #
Syntax sugar, конечно, красивый, но мне определенно кажется, что его использование в реальной жизни почти никогда ничем объективным не обосновано, а проблем это создает массу — 99% Ruby-программистов не поймут, как это работает, да и не могу я от них требовать, чтобы они такое понимали. В целом такие трюки скорее сводят всю стройную концепцию языка к чему-то perl-подобному а ля there's more than one way to do it.
+2
dapi #
Я надеюсь вы это про последний пример )
–3
GreyCat #
Я про всё вместе, к сожалению. Если посмотреть исторически — то этот самый «unary &» рождался долго и в муках — сначала в Ruby Extensions, затем в RoR, потом его таки наконец сделали нативным в Ruby 1.9 и только относительно недавно он попал (в виде бэкпорта!) в 1.8.7.

А кроме всего прочего, посмотрите сюда — Вы об этом «забыли» упомянуть в статье или не считаете это важным?
+1
dapi #
Насчет syntax sugar и скорости выполнения.

DSL и удобство чтения кода, а также приоритет быстроты написания кода перед его выполнением одни из основных принципов Ruby. Лично мне поэтому и нравится писать на Ruby.

Конечно ваше право их отвергать.
0
Rakoth #
> А кроме всего прочего, посмотрите сюда — Вы об этом «забыли» упомянуть в статье или не считаете это важным?

Ради интереса прогнал этот же тест на ruby 1.9.2, со стандартной реализацией Symbol#to_proc. Теперь могу сказать, что такая запись не только короче и приятнее взгляду, но и работает быстрее
pastie.org/1453436
+1
eugzol #
> 99% Ruby-программистов не поймут, как это работает

какие у вас странные руби-программисты :)
–2
dapi #
Symbol#to_proc появился в Ruby почти десять лет назад в версии1.1.

Амперсанд для последнего аргумента еще раньше.

Хорошая ссылка, надо накидать таких «по теме» в статью.
0
dapi #
Оп-па! Был дезинформирован, беру свои слова обратно.

Сейчас проверил в Ruby 1.8.6 метода Symbol#to_proc нет.
0
Meredian #
Официально он появился вообще только в 1.9 в стандарте. До этого жил в рельсах(или даже конкретно ActiveRecord?), а вообще код сам простой очень, уверен что сам метод начали использовать много раньше — неоднократно встречал просто объявленным в коде без подключения сторонних либ
+1
vite7 #
Вот за это я и люблю Ruby. Пишите про такие штуки — это очень интересно.
+1
valer00n #
Да-да. Именно за такие лаконичные и мощные конструкции я его тоже полюбил. Спасибо автору за статью!
+2
naryl #
> Расскажу как в Ruby работает такая элегантная конструкция:

> User.all.map &:name

Точно так же как в лиспах уже несколько десятков лет (mapcar #'name users). Правда, в CL с CLOS эта штука проста и логична, а в Ruby поведение слегка туманно.

Ruby — это немного обрезанный Лисп с человеческим лицом. Поздравляю, вы все — лисперы :)
+1
alsor #
На самом деле магия еще интереснее — здесь мы не ограничены передачей методов без параметров. При помощи &:symol можно передать метод принимающий любое количество аргументов.

Пусть у нас будет вот такой метод в массиве:
class Array
  def map_with_arguments (*args)
    a = []
    self.each do |e| 
      a << yield(e, *args)
    end
    a
  end
end


И вот такой метод в нашем юзере:
class User
  def initialize (name); @name = name; end

  def greeting (pre, suf)
    "#{pre}#{@name}#{suf}"
  end
end


Тогда вот это
a.map_with_arguments("Dear Mr. ", "!") {|el, pre, suf| el.greeting pre, suf}


можно написать вот так:
a.map_with_arguments("Dear Mr. ", "!", &:greeting)
0
alsor #
Сорри, забыл написать, что «a» это массив, что-то типа вот такого:
a = [User.new("Jack"), User.new("Bob")]


и тогда на выходе у нас:
["Dear Mr. Jack!", "Dear Mr. Bob!"]
0
rubyman #
Вот еще статья на эту тему: rubydev.ru/2011/01/ruby_enumerable_2_unary_ampersand/

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.