map (&: name) มีความหมายอย่างไรใน Ruby


496

ฉันพบรหัสนี้ในRailsCast :

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

อะไร(&:name)ในmap(&:name)ค่าเฉลี่ย?


122
ฉันเคยได้ยินสิ่งนี้เรียกว่า "pretzel ลำไส้ใหญ่" โดยวิธีการ
Josh Lee

6
ฮ่าฮ่า ฉันรู้ว่าในฐานะสัญลักษณ์ ฉันไม่เคยได้ยินชื่อเรียกว่า "เพรทเซล" แต่มันก็สมเหตุสมผล
DragonFax

74
การเรียกมันว่า "เพรทเซลโคลอน" นั้นทำให้เข้าใจผิดแม้ว่าจะติดหู ไม่มี "&:" ในทับทิม เครื่องหมายแอมเปอร์แซนด์ (&) คือ "โอเปอเรเตอร์เครื่องหมายแอนเดอร์แซนด์" ที่มีเครื่องหมายผลักเข้าด้วยกัน: หากมีสิ่งใดมันเป็น "สัญลักษณ์เพรทเซล" แค่พูด.
fontno

3
tags.map (&: name) เรียงลำดับจาก tags.map {| s | s.name}
kaushal sharma

3
"pretzel colon" ฟังดูเป็นเงื่อนไขทางการแพทย์ที่เจ็บปวด ... แต่ฉันชอบชื่อของสัญลักษณ์นี้ :)
zmorris

คำตอบ:


517

มันจดชวเลข tags.map(&:name.to_proc).join(' ')

ถ้าfooเป็นวัตถุที่มีto_procเมธอดคุณสามารถส่งผ่านไปยังเมธอด&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

41
นี่เป็นคำตอบที่ดีกว่าของฉัน
โอลิเวอร์เอ็น

91
tags.map (: name.to_proc) เป็นตัวย่อสำหรับ tags.map {| tag | tag.name}
Simone Carletti

5
นี่ไม่ใช่รหัสทับทิมที่ถูกต้องคุณยังจำเป็นต้องใช้&เช่นtags.map(&:name.to_proc).join(' ')
horseyguy

5
Symbol # to_proc มีการใช้งานใน C ไม่ใช่ใน Ruby แต่นั่นคือสิ่งที่ดูเหมือนใน Ruby
Andrew Grimm

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 ทุกอย่าง
Jared Beck

คุณสามารถแมปคอนสตรัคเตอร์ของคลาสอื่นได้&methodหรือไม่?
หลักการโฮโลแกรม

3
@ Finishingmove ใช่ฉันเดา ลองสิ่งนี้[1,2,3].map(&Array.method(:new))
Gerry


45

ในขณะที่ให้เราทราบว่า#to_procเวทมนตร์เครื่องหมายและสามารถทำงานกับชั้นเรียนใด ๆ ไม่ได้เป็นเพียงสัญลักษณ์ 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!"]

เครื่องหมายแอมเปอร์แซนด์&ทำงานโดยการส่งto_procข้อความบนตัวถูกดำเนินการซึ่งในโค้ดข้างต้นเป็นของคลาสอาเรย์ และตั้งแต่ฉันกำหนด#to_procmethod บน Array เส้นจะกลายเป็น:

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

นี่คือทองคำบริสุทธิ์!
kubak

38

มันจดชวเลข tags.map { |tag| tag.name }.join(' ')


ไม่มันอยู่ใน Ruby 1.8.7 ขึ้นไป
Chuck

มันเป็นสำนวนที่เรียบง่ายสำหรับแผนที่หรือ Ruby แปลความหมายของ '&' ในรูปแบบเฉพาะเสมอหรือไม่?
collimarco

7
@collimarco: ตามที่ jleedev กล่าวในคำตอบของเขา&ผู้ประกอบการเอกเรียกร้องto_procให้ถูกดำเนินการ ดังนั้นมันจึงไม่เฉพาะเจาะจงกับวิธีการทำแผนที่และอันที่จริงแล้วใช้ได้กับวิธีการใด ๆ ที่นำบล็อกและส่งผ่านอาร์กิวเมนต์หนึ่งรายการขึ้นไปไปยังบล็อก
Chuck

36
tags.map(&:name)

เป็นเช่นเดียวกับ

tags.map{|tag| tag.name}

&:name เพียงใช้สัญลักษณ์เป็นชื่อวิธีการที่จะเรียก


1
คำตอบที่ฉันกำลังมองหาไม่ใช่สำหรับ procs โดยเฉพาะ (แต่นั่นเป็นคำถามของผู้ร้องขอ)
matrim_c

คำตอบที่ดี! ชี้แจงให้ฉันดี
apadana

14

คำตอบของ Josh Lee นั้นเกือบจะถูกต้องยกเว้นว่ารหัส 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 ไม่ได้มีวิธีการด้วยตนเอง (ซึ่งก็คือ: แรก)


เมื่อ[[1,'a'],[2,'b'],[3,'c']].map(&:first)ถูกประหารชีวิต

  1. :firstเป็นวัตถุสัญลักษณ์ดังนั้นเมื่อ&:firstมอบให้กับวิธีการแผนที่เป็นพารามิเตอร์ Symbol # 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
คำตอบของ Josh Lee นั้นถูกต้องอย่างที่คุณเห็นโดยการ[1,2,3,4,5,6].inject(&:+)คาดหวังว่า - ฉีดคาดหวังแลมบ์ดาที่มีสองพารามิเตอร์ (บันทึกและรายการ) และ:+.to_procส่งมอบ - Proc.new |obj, *args| { obj.send(self, *args) }หรือ{ |m, o| m.+(o) }
Uri Agassi

11

มีสองสิ่งเกิดขึ้นที่นี่และสิ่งสำคัญคือต้องเข้าใจทั้งสองอย่าง

ตามที่อธิบายไว้ในคำตอบอื่น ๆSymbol#to_procวิธีการที่ถูกเรียก

แต่เหตุผลที่to_procถูกเรียกบนสัญลักษณ์ก็เพราะมันถูกส่งไปmapเป็นอาร์กิวเมนต์บล็อก การวาง&หน้าอาร์กิวเมนต์ในการเรียกเมธอดทำให้การส่งผ่านวิธีนี้ นี่เป็นความจริงสำหรับวิธีการทับทิมใด ๆ ไม่ใช่แค่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เพราะมันผ่านในขณะที่บล็อก เราสามารถแสดงสิ่งนี้ได้โดยพยายามส่ง 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บล็อกมันคาดว่า


นี่คือคำตอบที่ดีที่สุดที่ได้รับโดยสุจริต คุณอธิบายกลไกที่อยู่เบื้องหลังเครื่องหมายและทำไมเราถึงลงเอยด้วย proc ซึ่งฉันไม่ได้รับจนกว่าคำตอบของคุณ ขอบคุณ.
Fralcon

5

(&: ชื่อ) ย่อมาจาก (&: name.to_proc) ซึ่งเหมือนกับ tags.map{ |t| t.name }.join(' ')

to_proc มีการใช้งานจริงใน C


5

map (&: name)รับอ็อบเจกต์นับจำนวน (แท็กในกรณีของคุณ) และรันเมธอดชื่อสำหรับแต่ละอิลิเมนต์ / แท็กส่งออกแต่ละค่าที่ส่งคืนจากเมธอด

มันเป็นชวเลขสำหรับ

array.map { |element| element.name }

ซึ่งส่งคืนอาร์เรย์ของชื่อองค์ประกอบ (แท็ก)


3

มันเป็นพื้นเรียกวิธีการtag.nameในแต่ละแท็กในอาร์เรย์

มันเป็นชวเลขทับทิมที่เรียบง่าย


2

แม้ว่าเราจะมีคำตอบที่ดีอยู่แล้ว แต่หากมองผ่านมุมมองของผู้เริ่มต้นฉันต้องการเพิ่มข้อมูลเพิ่มเติม:

map (&: name) มีความหมายอย่างไรใน Ruby

ซึ่งหมายความว่าคุณกำลังผ่านวิธีอื่นเป็นพารามิเตอร์ไปยังฟังก์ชันแผนที่ (ในความเป็นจริงคุณกำลังส่งสัญลักษณ์ที่แปลงเป็น proc แต่นี่ไม่สำคัญในกรณีนี้)

สิ่งที่สำคัญคือคุณมีmethodชื่อnameที่จะใช้โดยวิธีการแผนที่เป็นอาร์กิวเมนต์แทนblockรูปแบบดั้งเดิม


2

ก่อน&:nameเป็นทางลัดสำหรับ&:name.to_procที่:name.to_procส่งกลับProc(สิ่งที่คล้ายกัน แต่ไม่เหมือนแลมบ์ดา) ว่าเมื่อเรียกว่ามีวัตถุเป็นอาร์กิวเมนต์ (แรก) เรียกnameวิธีการในวัตถุนั้น

ประการที่สองในขณะที่&ในdef foo(&block) ... endแปลงกระชากผ่านไปfooกับมันจะตรงข้ามเมื่อนำไปใช้กับProcProc

ดังนั้น&:name.to_procเป็นบล็อกที่ใช้วัตถุเป็นอาร์กิวเมนต์และเรียกร้องเป็นวิธีการที่มันคือname{ |o| o.name }


1

นี่:nameคือสัญลักษณ์ที่ชี้ไปที่วิธีการnameของวัตถุแท็ก เมื่อเราผ่าน&:nameไปmapก็จะถือว่าnameเป็นวัตถุ proc สำหรับสั้นtags.map(&:name)ทำหน้าที่เป็น:

tags.map do |tag|
  tag.name
end


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.