Что означает карта (&: 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 (&: 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(:==))

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


25
array.each{|e| foo(e)}короче еще :-) +1 в любом случае
— Джаред Бек

Не могли бы вы сопоставить конструктор другого класса, используя &method?
— голографический принцип

3
@финишмав да, наверное. Попробуйте это[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метод на массиве, строка становится:

[ '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 просто использует символ в качестве имени метода для вызова.


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)выражения.


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ожидаемый блок.


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

5

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

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


5

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

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

array.map { |element| element.name }

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


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 }.


1

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

tags.map do |tag|
  tag.name
end


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.