คุณสามารถจัดหาอาร์กิวเมนต์ให้กับไวยากรณ์ map (&: method) ใน Ruby ได้หรือไม่?


116

คุณอาจคุ้นเคยกับการชวเลข Ruby ต่อไปนี้ ( aคืออาร์เรย์):

a.map(&:method)

ตัวอย่างเช่นลองทำสิ่งต่อไปนี้ใน irb:

>> a=[:a, 'a', 1, 1.0]
=> [:a, "a", 1, 1.0]
>> a.map(&:class)
=> [Symbol, String, Fixnum, Float]

ไวยากรณ์คือชวเลขเป็นa.map(&:class)a.map {|x| x.class}

อ่านเพิ่มเติมเกี่ยวกับรูปแบบนี้ใน " อะไรแผนที่ (และชื่อ) หมายถึงในทับทิม "

ด้วยไวยากรณ์&:classคุณกำลังเรียกใช้เมธอดclassสำหรับแต่ละองค์ประกอบอาร์เรย์

คำถามของฉันคือคุณสามารถระบุข้อโต้แย้งในการเรียกใช้เมธอดได้หรือไม่? และถ้าเป็นเช่นนั้นอย่างไร?

ตัวอย่างเช่นคุณจะแปลงไวยากรณ์ต่อไปนี้ได้อย่างไร

a = [1,3,5,7,9]
a.map {|x| x + 2}

กับ&:ไวยากรณ์?

ฉันไม่ได้แนะนำว่า&:ไวยากรณ์จะดีกว่า ฉันสนใจแค่กลศาสตร์การใช้&:ไวยากรณ์กับอาร์กิวเมนต์

ฉันถือว่าคุณรู้ว่า+เป็นวิธีการในคลาสจำนวนเต็ม คุณสามารถลองสิ่งต่อไปนี้ใน irb:

>> a=1
=> 1
>> a+(1)
=> 2
>> a.send(:+, 1)
=> 2

คำตอบ:


139

คุณสามารถสร้างแพทช์ง่ายๆได้Symbolดังนี้:

class Symbol
  def with(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

ซึ่งจะช่วยให้คุณไม่เพียงทำสิ่งนี้:

a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11] 

แต่ยังมีสิ่งดีๆอื่น ๆ อีกมากมายเช่นการส่งผ่านพารามิเตอร์หลายตัว:

arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil] 
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil] 

และยังใช้งานได้injectซึ่งส่งผ่านสองอาร์กิวเมนต์ไปยังบล็อก:

%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde" 

หรือสิ่งที่เจ๋งสุด ๆ เมื่อส่ง [ชวเลข] ไปยังบล็อกชวเลข:

[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"] 
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

นี่คือบทสนทนาที่ฉันมีกับ @ArupRakshit อธิบายเพิ่มเติม:
คุณสามารถจัดหาอาร์กิวเมนต์ให้กับไวยากรณ์ map (&: method) ใน Ruby ได้หรือไม่?


ในฐานะที่เป็น @amcaplan ปัญหาในความคิดเห็นด้านล่างคุณสามารถสร้างไวยากรณ์สั้นถ้าคุณเปลี่ยนชื่อวิธีการในการwith callในกรณีนี้ทับทิมมีทางลัดในตัวสำหรับวิธีพิเศษ.()นี้

ดังนั้นคุณสามารถใช้ข้างต้นดังนี้:

class Symbol
  def call(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11] 

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

5
เยี่ยมมากขอให้นี่เป็นส่วนหนึ่งของ Ruby core!
Jikku Jose

6
@UriAgassi เพียงเพราะห้องสมุดจำนวนมากทำเช่นนี้ไม่ได้ทำให้เป็นแนวทางปฏิบัติที่ดี ในขณะที่Symbol#withอาจไม่มีอยู่ในไลบรารีหลักและการกำหนดวิธีการนั้นเป็นการทำลายล้างน้อยกว่าการกำหนดวิธีการที่มีอยู่ใหม่ แต่ก็ยังคงมีการเปลี่ยนแปลง (เช่นการเขียนทับ) การนำคลาสคอร์ของไลบรารีทับทิมไปใช้ การปฏิบัติควรทำเท่าที่จำเป็นและด้วยความระมัดระวังเป็นอย่างยิ่ง \ n \ n โปรดพิจารณาการสืบทอดจากคลาสที่มีอยู่และแก้ไขคลาสที่สร้างขึ้นใหม่ โดยทั่วไปแล้วจะได้ผลลัพธ์ที่เทียบเคียงได้โดยไม่มีผลข้างเคียงในเชิงลบจากการเปลี่ยนคลาสทับทิมหลัก
rudolph9

2
@ rudolph9 - ฉันขอแตกต่าง - คำจำกัดความของ "เขียนทับ" คือการเขียนทับบางสิ่งซึ่งหมายความว่าโค้ดที่เขียนนั้นไม่สามารถใช้ได้อีกต่อไปและเห็นได้ชัดว่าไม่ใช่กรณีนี้ เกี่ยวกับคำแนะนำของคุณในการสืบทอดSymbolคลาส - มันไม่ใช่เรื่องเล็กน้อย (ถ้าเป็นไปได้) เนื่องจากเป็นคลาสหลัก (เช่นไม่มีnewวิธีการ) และการใช้งานจะยุ่งยาก (ถ้าทำได้) ซึ่งจะเอาชนะ วัตถุประสงค์ของการเพิ่มประสิทธิภาพ ... หากคุณสามารถแสดงการนำไปใช้งานที่ใช้สิ่งนั้นและได้ผลลัพธ์ที่เทียบเคียงได้โปรดแบ่งปัน!
Uri Agassi

3
ฉันชอบวิธีแก้ปัญหานี้ แต่ฉันคิดว่าคุณสามารถสนุกกับมันได้มากกว่านี้ แทนการกำหนดวิธีการกำหนดwith callจากนั้นคุณสามารถทำสิ่งต่างๆเช่นa.map(&:+.(2))ตั้งแต่object.()ใช้#callวิธีการ และในขณะที่คุณอยู่ที่นั่นคุณสามารถเขียนเรื่องสนุก ๆ เช่น:+.(2).(3) #=> 5- รู้สึกเหมือน LISPy ไม่ใช่เหรอ?
amcaplan

2
ชอบที่จะเห็นสิ่งนี้เป็นหลักซึ่งเป็นรูปแบบทั่วไปที่สามารถใช้น้ำตาลได้แผนที่ (&: foo)
Stephen

48

a.map(&2.method(:+))ตัวอย่างเช่นคุณสามารถทำได้

Arup-iMac:$ pry
[1] pry(main)> a = [1,3,5,7,9]
=> [1, 3, 5, 7, 9]
[2] pry(main)> a.map(&2.method(:+))
=> [3, 5, 7, 9, 11]
[3] pry(main)> 

นี่คือวิธีการทำงาน: -

[3] pry(main)> 2.method(:+)
=> #<Method: Fixnum#+>
[4] pry(main)> 2.method(:+).to_proc
=> #<Proc:0x000001030cb990 (lambda)>
[5] pry(main)> 2.method(:+).to_proc.call(1)
=> 3

2.method(:+)ให้Methodวัตถุ จากนั้น &ใน2.method(:+)ความเป็นจริงการเรียก#to_procวิธีการซึ่งจะทำให้มันเป็นProcวัตถุ จากนั้นทำตามWhat do you call the &: operator in Ruby? .


ฉลาดใช้! สมมติว่าการเรียกใช้เมธอดสามารถใช้ได้ทั้งสองวิธี (เช่น arr [element] .method (param) === param.method (arr [element])) หรือฉันสับสน?
Kostas Rousis

@rkon ฉันไม่ได้รับคำถามของคุณด้วย แต่ถ้าคุณเห็นPryผลลัพธ์ด้านบนคุณจะได้รับมันทำงานอย่างไร
อรุณรักชิต

5
@rkon มันใช้ไม่ได้ทั้งสองวิธี มันใช้งานได้ในกรณีนี้เนื่องจาก+เป็นการสับเปลี่ยน
sawa

คุณจะจัดหาอาร์กิวเมนต์หลายรายการได้อย่างไร? เช่นในกรณีนี้: a.map {| x | x.method (1,2,3)}
Zack Xu

1
นั่นคือประเด็นของฉัน @sawa :) มันสมเหตุสมผลกับ + แต่จะไม่ใช้วิธีอื่นหรือสมมติว่าคุณต้องการหารแต่ละจำนวนด้วย X
Kostas Rousis

11

ในฐานะที่เป็นโพสต์ที่เชื่อมโยงไปยังยืนยันa.map(&:class)ไม่ได้จดชวเลขเป็นแต่สำหรับa.map {|x| x.class}a.map(&:class.to_proc)

ซึ่งหมายความว่าto_procสิ่งที่เรียกตามตัว&ดำเนินการ

ดังนั้นคุณสามารถให้โดยตรงProcแทน:

a.map(&(Proc.new {|x| x+2}))

ฉันรู้ว่าส่วนใหญ่อาจจะพ่ายแพ้นี้วัตถุประสงค์ของคำถามของคุณ แต่ฉันไม่สามารถดูวิธีการอื่น ๆ รอบมัน - to_procมันไม่ใช่ว่าคุณระบุว่าวิธีการที่จะเรียกว่าคุณก็ผ่านมันเป็นสิ่งที่ตอบสนองต่อ


1
โปรดทราบว่าคุณสามารถตั้งค่า procs ให้กับตัวแปรท้องถิ่นและส่งต่อไปยังแผนที่ได้ my_proc = Proc.new{|i| i + 1},[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9

10

คำตอบสั้น ๆ : ไม่

ตามคำตอบของ @ rkon คุณสามารถทำได้:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]

9
คุณพูดถูก แต่ฉันไม่คิดว่า&->(_){_ + 2}จะสั้นกว่า{|x| x + 2}นี้
sawa

1
ไม่ใช่นั่นคือสิ่งที่ @rkon พูดในคำตอบของเขาดังนั้นฉันจึงไม่พูดซ้ำ
Agis

2
@ แม้ว่าคำตอบของคุณจะไม่สั้น แต่ก็ดูดีขึ้น
Jikku Jose

1
นั่นเป็นทางออกที่ยอดเยี่ยม
BenMorganIO

6

มีตัวเลือกเนทีฟอีกตัวหนึ่งสำหรับการแจงนับซึ่งค่อนข้างสำหรับสองข้อโต้แย้งในความคิดของฉัน คลาสEnumerableมีวิธีการwith_objectที่ส่งกลับอีกEnumerableที่จะส่งกลับอีกแล้ว

ดังนั้นคุณสามารถโทร &ตัวดำเนินการสำหรับวิธีการที่มีแต่ละรายการและวัตถุเป็นอาร์กิวเมนต์

ตัวอย่าง:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+) # => [3, 5, 7, 9, 11]

ในกรณีที่คุณต้องการข้อโต้แย้งเพิ่มเติมคุณควรทำซ้ำการดำเนินการ แต่มันน่าเกลียดในความคิดของฉัน:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+).to_enum.with_object(5).map(&:+) # => [8, 10, 12, 14, 16]

5

แทนที่จะแก้ไขคลาสหลักด้วยตัวเองเหมือนในคำตอบที่ยอมรับการใช้ฟังก์ชันการทำงานของอัญมณี Facetsนั้นสั้นและสะอาดกว่า:

require 'facets'
a = [1,3,5,7,9]
a.map &:+.(2)

0

ฉันไม่แน่ใจเกี่ยวกับสิ่งที่Symbol#withโพสต์ไปแล้วฉันทำให้มันง่ายขึ้นเล็กน้อยและทำงานได้ดี:

class Symbol
  def with(*args, &block)
    lambda { |object| object.public_send(self, *args, &block) }
  end
end

(ยังใช้public_sendแทนsendเพื่อป้องกันการโทรเมธอดส่วนตัวcallerทับทิมก็ใช้อยู่แล้วจึงทำให้สับสน)

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