Что означает карта (&: name) в Ruby?

496

Я нашел этот код в RailsCast :

def tag_names
  @tag_names || tags.map(&:name).join(' ')
end

Что значит (&:name)в map(&:name)?

collimarco
источник
122
Кстати, я слышал, что это называется «крендель с толстой кишкой».
Джош Ли
6
Ха - ха. Я знаю это как амперсанд. Я никогда не слышал, чтобы это называлось «крендель», но это имеет смысл.
DragonFax
74
Называть его «крендель толстой кишки» вводит в заблуждение, хотя и цепляет. В рубине нет слова "&:". Амперсанд (&) является «унарным оператором амперсанда» со значком: Во всяком случае, это «символ кренделя». Просто говорю.
fontno
3
tags.map (&: name) является сортировкой из tags.map {| s | s.name}
Каушал Шарма
3
«Крендель толстой кишки» звучит как болезненное заболевание ... но мне нравится название этого символа :)
zmorris

Ответы:

517

Это сокращение для tags.map(&:name.to_proc).join(' ')

Если fooэто объект с to_procметодом, то вы можете передать его методу as &foo, который будет вызывать foo.to_procи использовать его как блок метода.

Symbol#to_procМетод был первоначально добавлен ActiveSupport , но был интегрирован в Руби 1.8.7. Это его реализация:

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end
Джош Ли
источник
42
Это лучший ответ, чем мой.
Оливер Н.
91
tags.map (: name.to_proc) сам по себе является сокращением для tags.map {| tag | tag.name}
Симона Карлетти
5
это недействительный код рубина, вам все еще нужен &, т.е.tags.map(&:name.to_proc).join(' ')
horseyguy
5
Символ # to_proc реализован в C, а не в Ruby, но именно так он будет выглядеть в Ruby.
Эндрю Гримм
5
@AndrewGrimm он был впервые добавлен в Ruby on Rails с использованием этого кода. Затем он был добавлен в качестве встроенной функции ruby ​​в версии 1.8.7.
Кэмерон Мартин
175

Еще одна классная стенография, неизвестная многим,

array.each(&method(:foo))

что является сокращением для

array.each { |element| foo(element) }

При вызове method(:foo)мы взяли Methodобъект self, представляющий его fooметод, и использовали его, &чтобы показать, что у него есть to_proc метод, который преобразует его в Proc.

Это очень полезно, когда вы хотите делать вещи без стилей. Пример - проверить, есть ли какая-либо строка в массиве, равная этой строке "foo". Есть общепринятый способ:

["bar", "baz", "foo"].any? { |str| str == "foo" }

И есть бессмысленный способ:

["bar", "baz", "foo"].any?(&"foo".method(:==))

Предпочтительный способ должен быть наиболее читабельным.

Gerry
источник
25
array.each{|e| foo(e)}короче еще :-) +1 в любом случае
Джаред Бек
Не могли бы вы сопоставить конструктор другого класса, используя &method?
голографический принцип
3
@финишмав да, наверное. Попробуйте это[1,2,3].map(&Array.method(:new))
Джерри
78

Это эквивалентно

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end
Софи Альперт
источник
45

Хотя отметим также, что #to_procмагия амперсандов может работать с любым классом, а не только с Symbol. Многие Rubyists предпочитают определять #to_procв классе Array:

class Array
  def to_proc
    proc { |receiver| receiver.send *self }
  end
end

# And then...

[ 'Hello', 'Goodbye' ].map &[ :+, ' world!' ]
#=> ["Hello world!", "Goodbye world!"]

Ampersand &работает, отправляя to_procсообщение на свой операнд, который в приведенном выше коде относится к классу Array. И так как я определил #to_procметод на массиве, строка становится:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Борис Стиницкий
источник
Это чистое золото!
Кубак
38

Это сокращение для tags.map { |tag| tag.name }.join(' ')

Оливер Н.
источник
Нет, это в Ruby 1.8.7 и выше.
Чак
Это простая идиома для карты или Ruby всегда интерпретирует '&' определенным образом?
Коллимарко
7
@collimarco: Как говорит Джлидев в своем ответе, унарный &оператор вызывает to_procсвой операнд. Так что он не является специфичным для метода map и фактически работает с любым методом, который принимает блок и передает один или несколько аргументов в блок.
Чак
36
tags.map(&:name)

такой же как

tags.map{|tag| tag.name}

&:name просто использует символ в качестве имени метода для вызова.

Albert.Qing
источник
1
Ответ, который я искал, а не специально для проков (но это был вопрос запрашивающих)
matrim_c
Хороший ответ! разъяснил для меня хорошо.
Ападана
14

Ответ Джоша Ли является почти правильным, за исключением того, что эквивалентный код Ruby должен был выглядеть следующим образом.

class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

не

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end

С помощью этого кода, когда print [[1,'a'],[2,'b'],[3,'c']].map(&:first)выполняется, Ruby разбивает первый ввод [1,'a']на 1 и 'a', чтобы дать obj1 и args*'a', чтобы вызвать ошибку, так как объект Fixnum 1 не имеет метода self (который: first).


Когда [[1,'a'],[2,'b'],[3,'c']].map(&:first)выполняется;

  1. :firstявляется объектом Symbol, поэтому когда &:firstон передается методу карты в качестве параметра, вызывается Symbol # to_proc.

  2. Карта отправляет сообщение о вызове: first.to_proc с параметром [1,'a'], например, :first.to_proc.call([1,'a'])выполняется.

  3. Процедура to_proc в классе Symbol отправляет сообщение отправки в объект массива ( [1,'a']) с параметром (: first), например, [1,'a'].send(:first)выполняется.

  4. перебирает остальные элементы [[1,'a'],[2,'b'],[3,'c']]объекта.

Это то же самое, что выполнение [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)выражения.

prosseek
источник
1
Ответ Джоша Ли абсолютно верен, как вы можете видеть, подумав о том, что [1,2,3,4,5,6].inject(&:+)инъекция ожидает лямбду с двумя параметрами (записку и предмет) и :+.to_procдоставляет ее - Proc.new |obj, *args| { obj.send(self, *args) }или{ |m, o| m.+(o) }
Ури Агасси
11

Здесь происходят две вещи, и важно понимать обе.

Как описано в других ответах, Symbol#to_proc метод вызывается.

Но причина to_procвызова символа состоит в том, что он передается mapкак аргумент блока. Помещение &перед аргументом в вызове метода приводит к тому, что он передается таким образом. Это верно для любого метода Ruby, не только mapс символами.

def some_method(*args, &block)
  puts "args: #{args.inspect}"
  puts "block: #{block.inspect}"
end

some_method(:whatever)
# args: [:whatever]
# block: nil

some_method(&:whatever)
# args: []
# block: #<Proc:0x007fd23d010da8>

some_method(&"whatever")
# TypeError: wrong argument type String (expected Proc)
# (String doesn't respond to #to_proc)

SymbolПреобразуется к Procпотому , что она передается в качестве блока. Мы можем показать это, пытаясь передать процедуру .mapбез амперсанда:

arr = %w(apple banana)
reverse_upcase = proc { |i| i.reverse.upcase }
reverse_upcase.is_a?(Proc)
=> true

arr.map(reverse_upcase)
# ArgumentError: wrong number of arguments (1 for 0)
# (map expects 0 positional arguments and one block argument)

arr.map(&reverse_upcase)
=> ["ELPPA", "ANANAB"]

Даже если его не нужно преобразовывать, метод не будет знать, как его использовать, поскольку он ожидает аргумент блока. Передача с ним &дает .mapожидаемый блок.

devpuppy
источник
Это, честно говоря, лучший ответ. Вы объясняете механизм, стоящий за амперсандом, и почему мы в конечном итоге получаем процедуру, которую я не получил до вашего ответа. Спасибо.
Фралкон
5

(&: name) - это сокращение от (&: name.to_proc), оно совпадает с tags.map{ |t| t.name }.join(' ')

to_proc фактически реализован в C

Тесси
источник
5

карта (&: название) берет перечислимый объект (тэги в вашем случае) и запускает метод name для каждого элемента / тэга, выводя каждое возвращаемое значение из метода.

Это сокращение для

array.map { |element| element.name }

который возвращает массив имен элементов (тегов)

Sunda
источник
3

Он в основном выполняет вызов метода tag.nameдля каждого тега в массиве.

Это упрощенная рубиновая стенография.

Олалекан Согунле
источник
2

Хотя у нас уже есть отличные ответы, глядя на перспективу новичка, я хотел бы добавить дополнительную информацию:

Что означает карта (&: name) в Ruby?

Это означает, что вы передаете другой метод в качестве параметра функции map. (На самом деле вы передаете символ, который превращается в процесс. Но это не так важно в данном конкретном случае).

Важно то, что у вас есть methodимя, nameкоторое будет использоваться методом карты в качестве аргумента вместо традиционного blockстиля.

Джонатан Дуарте
источник
2

Во-первых, &:nameэто ярлык для &:name.to_proc, где :name.to_procвозвращает Proc(что-то похожее, но не идентичное лямбда-выражению), которое при вызове с объектом в качестве (первого) аргумента вызывает nameметод для этого объекта.

Во-вторых, в то время как &in def foo(&block) ... endпреобразует блок, переданный в fooa Proc, он делает обратное при применении к a Proc.

Таким образом, &:name.to_procэто блок , который принимает объект в качестве аргумента и вызывает nameметод на него, то есть { |o| o.name }.

Christoph
источник
1

Вот :nameсимвол, который указывает на метод nameобъекта тега. Когда мы перейдем &:nameк map, он будет рассматриваться nameкак объект proc. Для краткости tags.map(&:name)действует как:

tags.map do |tag|
  tag.name
end
timlentse
источник
0

Это так же, как показано ниже:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Навин Кумар
источник