Що означає карта (&: name) у Ruby?


496

Я знайшов цей код на RailsCast :

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

Що робить (&:name)в map(&:name)середньому?


122
Я, до речі, чув це, що називається «крендельна кишка».
Джош Лі

6
Ха-ха. Я це знаю як "Амперсанд". Я ніколи не чув, щоб це називалося "кренделем", але це має сенс.
DragonFax

74
Називати його "кишечником кишечника" - вводить в оману, хоча і влучно. У рубіні немає "&:". Амперсанд (&) - це "одинарний оператор амперсанду" із символом, що з'єднується:. Якщо що, це "символ кренделі". Просто кажу.
fontno

3
tags.map (&: ім'я) є сортуванням з tags.map {| s | s.name}
kaushal sharma

3
"крендельова кишка" звучить як хворобливий стан здоров'я ... але мені подобається назва цього символу :)
zmorris

Відповіді:


517

Це стенограма для tags.map(&:name.to_proc).join(' ')

Якщо fooце об'єкт із to_procметодом, то ви можете передати його методу як &foo, який буде викликати foo.to_procі використовувати це як блок методу.

Спочатку Symbol#to_procметод був доданий ActiveSupport, але він був інтегрований у Ruby 1.8.7. Це його реалізація:

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

41
Це краща відповідь, ніж моя.
Олівер Н.

91
tags.map (: name.to_proc) - сама стенограма для tags.map {| тег | tag.name}
Симоне Карлетті

5
це не дійсний код рубіну, вам все одно потрібен &, тобтоtags.map(&:name.to_proc).join(' ')
horseyguy

5
Символ # to_proc реалізований в C, а не в Ruby, але це було б схоже на Ruby.
Ендрю Грімм

5
@AndrewGrimm його вперше додали в Ruby on Rails, використовуючи цей код. Потім він був доданий як функція рідного рубіна у версії 1.8.7.
Камерон Мартін

174

Ще одна класна стенограма, невідома багатьом, є

array.each(&method(:foo))

що є скороченням

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

Зателефонувавши, method(:foo)ми взяли Methodоб'єкт, selfякий представляє його fooметод, і використовували &для позначення того, що у нього є to_proc метод, який перетворює його в a Proc.

Це дуже корисно, коли ви хочете робити стиль без точок . Приклад - перевірити, чи є в масиві який-небудь рядок, який дорівнює рядку "foo". Існує звичайний спосіб:

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

І є безпроблемний спосіб:

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

Кращий спосіб повинен бути найбільш читабельним.


25
array.each{|e| foo(e)}все ж коротше :-) +1 у будь-якому разі
Джаред Бек

Не могли б ви скопіювати конструктор іншого класу за допомогою &method?
голографічний принцип

3
@finishingmove так, я думаю. Спробуйте це[1,2,3].map(&Array.method(:new))
Геррі


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метод у Array, рядок стає:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }

Це чисте золото!
кубак

38

Це стенограма для tags.map { |tag| tag.name }.join(' ')


Ні, це в Ruby 1.8.7 і вище.
Чак

Це проста ідіома для карти чи Рубі завжди інтерпретує "&" певним чином?
collimarco

7
@collimarco: Як говорить jleedev у своїй відповіді, одинарний &оператор викликає to_procсвій операнд. Тож це не конкретно для методу map, а насправді працює над будь-яким методом, який бере блок і передає блоку один або більше аргументів.
Чак

36
tags.map(&:name)

те саме, що

tags.map{|tag| tag.name}

&:name просто використовує символ як ім'я методу, який потрібно викликати.


1
Відповідь я шукав, а не спеціально для користувачів (але це було запитання запитувачів)
matrim_c

Гарна відповідь! уточнили для мене добре.
apadana

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', щоб викликати помилку, оскільки об'єкт 1 Fixnum не має методу self (який є: першим).


Коли [[1,'a'],[2,'b'],[3,'c']].map(&:first)виконується;

  1. :firstє об'єктом Symbol, тому, коли &:firstметод вказаний як параметр як параметр, викликається символ # to_proc.

  2. карта надсилає повідомлення про виклик: first.to_proc з параметром [1,'a'] , наприклад, :first.to_proc.call([1,'a'])виконується.

  3. Процедура to_proc класу Symbol надсилає повідомлення відправника об’єкту масиву ([1,'a'] ) з параметром (: перший), наприклад, [1,'a'].send(:first)виконується.

  4. повторює решту елементів в [[1,'a'],[2,'b'],[3,'c']]об'єкті.

Це те саме, що виконувати [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)вираз.


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блок, який він очікує.


Це, чесно, найкраща відповідь. Ви пояснюєте механізм, який стоїть за амперсандом, і чому ми закінчуємо процедуру, яку я не отримав до вашої відповіді. Дякую.
Fralcon

5

(&: name) - скорочення (&: name.to_proc) - це те саме, що tags.map{ |t| t.name }.join(' ')

to_proc реально реалізований в C


5

map (&: name) приймає незліченний об'єкт (теги у вашому випадку) та запускає метод імені для кожного елемента / тегу, виводячи кожне повернене значення з методу.

Це скорочення для

array.map { |element| element.name }

який повертає масив імен елементів (тегів)


3

В основному виконується виклик методу tag.nameдля кожного тегу в масиві.

Це спрощена рубінова стенограма.


2

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

Що означає карта (&: name) у Ruby?

Це означає, що ви передаєте інший метод як параметр функції map. (Насправді ви передаєте символ, який перетворюється на процедуру. Але це не так важливо в конкретному випадку).

Важливо, що у вас є methodім’я, nameяке буде використано методом map як аргумент замість традиційного blockстилю.


2

По-перше, &:nameце ярлик для &:name.to_proc, де :name.to_procповертається a Proc(щось схоже, але не тотожне лямбда), яке при виклику з об'єктом як (перший) аргумент викликає nameметод на цьому об'єкті.

По- друге, в той час як &в def foo(&block) ... endзвернених блок - передається fooдо Proc, він робить протилежне при застосуванні доProc .

Таким чином, &:name.to_procце блок, який приймає об’єкт як аргумент і викликає на ньому nameметод, тобто { |o| o.name }.


1

Ось :nameсимвол, який вказує на метод nameоб’єкта тегів. Коли ми перейдемо &:nameдо mapнього, він буде розглядатись nameяк об'єкт прок. Якщо коротко, він tags.map(&:name)виконує функції:

tags.map do |tag|
  tag.name
end


0

Це те саме, що нижче:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.