Apa arti peta (&: nama) di Ruby?

496

Saya menemukan kode ini di RailsCast :

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

Apa yang (&:name)di map(&:name)berarti?

collimarco
sumber
122
Saya pernah mendengar ini disebut "pretzel colon".
Josh Lee
6
Ha ha. Saya tahu itu sebagai Ampersand. Saya belum pernah mendengarnya disebut "pretzel" tetapi itu masuk akal.
DragonFax
74
Menyebutnya "pretzel colon" menyesatkan, meskipun menarik. Tidak ada "&:" di ruby. Ampersand (&) adalah "operator ampersand unary" dengan simbol push together :. Jika ada, itu adalah "simbol pretzel". Hanya mengatakan.
fontno
3
tags.map (&: name) adalah pengurutan dari tags.map {| s | s.name}
kaushal sharma
3
"pretzel colon" terdengar seperti kondisi medis yang menyakitkan ... tapi saya suka nama untuk simbol ini :)
zmorris

Jawaban:

517

Ini singkatan untuk tags.map(&:name.to_proc).join(' ')

Jika fooobjek dengan to_procmetode, maka Anda bisa meneruskannya ke metode sebagai &foo, yang akan memanggil foo.to_procdan menggunakannya sebagai blok metode.

The Symbol#to_procMetode ini awalnya ditambahkan oleh ActiveSupport tetapi telah diintegrasikan ke dalam Ruby 1.8.7. Ini implementasinya:

class Symbol
  def to_proc
    Proc.new do |obj, *args|
      obj.send self, *args
    end
  end
end
Josh Lee
sumber
42
Ini jawaban yang lebih baik dari saya.
Oliver N.
91
tags.map (: name.to_proc) sendiri merupakan kependekan dari tags.map {| tag | tag.name}
Simone Carletti
5
ini bukan kode ruby ​​yang valid, Anda masih memerlukan &, yaitutags.map(&:name.to_proc).join(' ')
horseyguy
5
Simbol # to_proc diimplementasikan dalam C, bukan di Ruby, tetapi akan terlihat seperti di Ruby.
Andrew Grimm
5
@AndrewGrimm pertama kali ditambahkan di Ruby on Rails, menggunakan kode itu. Itu kemudian ditambahkan sebagai fitur ruby ​​asli di versi 1.8.7.
Cameron Martin
175

Steno keren lainnya, tidak diketahui banyak orang, adalah

array.each(&method(:foo))

yang merupakan singkatan untuk

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

Dengan memanggil method(:foo)kita mengambil Methodobjek dari selfyang mewakili foometodenya, dan menggunakan &untuk menandakan bahwa ia memiliki to_proc metode yang mengubahnya menjadi a Proc.

Ini sangat berguna ketika Anda ingin melakukan hal - hal gaya point-free . Contohnya adalah untuk memeriksa apakah ada string dalam array yang sama dengan string "foo". Ada cara konvensional:

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

Dan ada cara bebas-point:

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

Cara yang disukai harus yang paling mudah dibaca.

Gerry
sumber
25
array.each{|e| foo(e)}masih lebih pendek :-) +1 lagian
Jared Beck
Bisakah Anda memetakan konstruktor dari kelas lain menggunakan &method?
prinsip holografik
3
@ finishingmove ya saya kira. Coba ini[1,2,3].map(&Array.method(:new))
Gerry
78

Ini setara dengan

def tag_names
  @tag_names || tags.map { |tag| tag.name }.join(' ')
end
Sophie Alpert
sumber
45

Sementara mari kita perhatikan juga bahwa ampersand #to_procmagic dapat bekerja dengan semua kelas, bukan hanya Symbol. Banyak Rubyist memilih untuk mendefinisikan #to_procpada kelas Array:

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

# And then...

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

Ampersand &bekerja dengan mengirim to_procpesan pada operannya, yang, dalam kode di atas, adalah dari kelas Array. Dan karena saya mendefinisikan #to_procmetode pada Array, barisnya menjadi:

[ 'Hello', 'Goodbye' ].map { |receiver| receiver.send( :+, ' world!' ) }
Boris Stitnicky
sumber
Ini emas murni!
kubak
38

Ini singkatan untuk tags.map { |tag| tag.name }.join(' ')

Oliver N.
sumber
Tidak, ini ada di Ruby 1.8.7 ke atas.
Chuck
Apakah ini idiom sederhana untuk peta atau Ruby selalu mengartikan '&' dengan cara tertentu?
collimarco
7
@collimarco: Seperti yang dikatakan jleedev dalam jawabannya, &operator unary memanggil to_procoperannya. Jadi itu tidak spesifik untuk metode peta, dan pada kenyataannya bekerja pada metode apa pun yang mengambil blok dan melewati satu atau lebih argumen ke blok.
Chuck
36
tags.map(&:name)

sama dengan

tags.map{|tag| tag.name}

&:name hanya menggunakan simbol sebagai nama metode untuk dipanggil.

Albert
sumber
1
Jawaban yang saya cari, bukan khusus untuk procs (tapi itu adalah pertanyaan pemohon)
matrim_c
Jawaban bagus! diperjelas untuk saya dengan baik.
apadana
14

Jawaban Josh Lee hampir benar kecuali bahwa kode Ruby yang setara seharusnya adalah sebagai berikut.

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

tidak

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

Dengan kode ini, ketika print [[1,'a'],[2,'b'],[3,'c']].map(&:first)dijalankan, Ruby membagi input pertama [1,'a']menjadi 1 dan 'a' untuk memberikan obj1 dan args*'a' untuk menyebabkan kesalahan karena objek Fixnum 1 tidak memiliki metode sendiri (yaitu: pertama).


Kapan [[1,'a'],[2,'b'],[3,'c']].map(&:first)dieksekusi;

  1. :firstadalah objek Simbol, jadi ketika &:firstdiberikan kepada metode peta sebagai parameter, Simbol # to_proc dipanggil.

  2. map mengirim pesan panggilan ke: first.to_proc dengan parameter [1,'a'], mis :first.to_proc.call([1,'a']). dijalankan.

  3. prosedur to_proc di kelas Simbol mengirimkan pesan kirim ke objek array ( [1,'a']) dengan parameter (: pertama), misalnya, [1,'a'].send(:first)dijalankan.

  4. iterates atas sisa elemen dalam [[1,'a'],[2,'b'],[3,'c']]objek.

Ini sama dengan mengeksekusi [[1,'a'],[2,'b'],[3,'c']].map(|e| e.first)ekspresi.

prosseek
sumber
1
Jawaban Josh Lee benar - benar benar, seperti yang dapat Anda lihat dengan memikirkan [1,2,3,4,5,6].inject(&:+)- menyuntikkan mengharapkan lambda dengan dua parameter (memo dan item) dan :+.to_procmengirimkannya - Proc.new |obj, *args| { obj.send(self, *args) }atau{ |m, o| m.+(o) }
Uri Agassi
11

Ada dua hal yang terjadi di sini, dan penting untuk memahami keduanya.

Seperti dijelaskan dalam jawaban lain, Symbol#to_procmetode ini dipanggil.

Tapi alasannya to_procdipanggil pada simbol adalah karena dilewatkan mapsebagai argumen blok. Menempatkan &di depan argumen dalam pemanggilan metode menyebabkannya diteruskan dengan cara ini. Ini berlaku untuk metode Ruby apa pun, tidak hanya mapdengan simbol.

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)

Akan Symboldikonversi ke Prockarena dilewatkan sebagai blok. Kami dapat menunjukkan ini dengan mencoba meneruskan proc ke .maptanpa ampersand:

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"]

Meskipun tidak perlu dikonversi, metode ini tidak akan tahu bagaimana menggunakannya karena mengharapkan argumen blok. Melewati dengan &memberi .mapblok yang diharapkannya.

devpuppy
sumber
Ini jujur ​​jawaban terbaik yang diberikan. Anda menjelaskan mekanisme di belakang ampersand dan mengapa kita berakhir dengan proc, yang saya tidak dapatkan sampai jawaban Anda. Terima kasih.
Fralcon
5

(&: name) adalah kependekan dari (&: name.to_proc) sama dengan tags.map{ |t| t.name }.join(' ')

to_proc sebenarnya diimplementasikan dalam C

Tessie
sumber
5

peta (&: nama) mengambil objek enumerable (memberi tag pada kasus Anda) dan menjalankan metode nama untuk setiap elemen / tag, menghasilkan setiap nilai yang dikembalikan dari metode.

Ini adalah singkatan untuk

array.map { |element| element.name }

yang mengembalikan array nama elemen (tag)

Sunda
sumber
3

Ini pada dasarnya menjalankan pemanggilan metode tag.namepada setiap tag dalam array.

Ini adalah steno ruby ​​yang disederhanakan.

Olalekan Sogunle
sumber
2

Meskipun kami sudah memiliki jawaban yang luar biasa, melihat melalui perspektif seorang pemula saya ingin menambahkan informasi tambahan:

Apa arti peta (&: nama) di Ruby?

Ini berarti, bahwa Anda meneruskan metode lain sebagai parameter ke fungsi peta. (Pada kenyataannya Anda melewati simbol yang akan dikonversi menjadi proc. Tapi ini tidak begitu penting dalam kasus khusus ini).

Yang penting adalah Anda memiliki methodnama nameyang akan digunakan oleh metode peta sebagai argumen alih-alih blockgaya tradisional .

Jonathan Duarte
sumber
2

Pertama, &:nameadalah jalan pintas untuk &:name.to_proc, di mana :name.to_procmengembalikan Proc(sesuatu yang mirip, tetapi tidak identik dengan lambda) yang ketika dipanggil dengan objek sebagai argumen (pertama), memanggil namemetode pada objek itu.

Kedua, sementara &di def foo(&block) ... endmualaf blok diteruskan ke fooke Proc, itu tidak sebaliknya bila diterapkan pada Proc.

Dengan demikian, &:name.to_procadalah blok yang mengambil objek sebagai argumen dan memanggil namemetode di atasnya, yaitu { |o| o.name }.

Christoph
sumber
1

Berikut :nameadalah simbol yang menunjuk ke metode nameobjek tag. Ketika kita beralih &:nameke map, itu akan memperlakukan namesebagai objek proc. Singkatnya, tags.map(&:name)bertindak sebagai:

tags.map do |tag|
  tag.name
end
timlentse
sumber
1

itu berarti

array.each(&:to_sym.to_proc)
mminski
sumber
0

Sama seperti di bawah ini:

def tag_names
  if @tag_names
    @tag_names
  else
    tags.map{ |t| t.name }.join(' ')
end
Naveen Kumar
sumber