ใน Ruby Magic เราชอบที่จะดำดิ่งสู่ความมหัศจรรย์เบื้องหลังสิ่งที่เราใช้ทุกวันเพื่อทำความเข้าใจวิธีการทำงาน ในฉบับนี้ เราจะมาสำรวจความแตกต่างระหว่างบล็อก โปรค และแลมบ์ดา
ในภาษาโปรแกรมที่มีฟังก์ชันระดับเฟิร์สคลาส ฟังก์ชันสามารถเก็บไว้ในตัวแปรและส่งต่อเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอื่นๆ ฟังก์ชันยังใช้ฟังก์ชันอื่นเป็นค่าที่ส่งกลับได้อีกด้วย
การปิดเป็นฟังก์ชันชั้นหนึ่งที่มีสภาพแวดล้อม สภาพแวดล้อมคือการแมปกับตัวแปรที่มีอยู่เมื่อมีการสร้างการปิด การปิดจะคงการเข้าถึงตัวแปรเหล่านี้ไว้ แม้ว่าจะมีการกำหนดไว้ในขอบเขตอื่น
Ruby ไม่มีฟังก์ชันระดับเฟิร์สคลาส แต่มีฝาปิดในรูปแบบของบล็อก โปรค และแลมบ์ดา บล็อกใช้สำหรับส่งบล็อกของโค้ดไปยังเมธอด และ procs และ lambda อนุญาตให้จัดเก็บบล็อกของโค้ดในตัวแปร
บล็อค
ใน Ruby บล็อก เป็นส่วนย่อยของรหัสที่สามารถสร้างเพื่อดำเนินการในภายหลัง บล็อกถูกส่งไปยังวิธีการที่ให้ผลลัพธ์ภายใน do
และ end
คีย์เวิร์ด หนึ่งในตัวอย่างมากมายคือ #each
วิธีการซึ่งวนรอบวัตถุที่นับได้
[1,2,3].each do |n|
puts "#{n}!"
end
[1,2,3].each { |n| puts "#{n}!" } # the one-line equivalent.
ในตัวอย่างนี้ บล็อกจะถูกส่งไปยัง Array#each
เมธอด ซึ่งรันบล็อกสำหรับแต่ละรายการในอาร์เรย์และพิมพ์ไปยังคอนโซล
def each
i = 0
while i < size
yield at(i)
i += 1
end
end
ในตัวอย่างแบบง่ายของ Array#each
ใน while
วนซ้ำ yield
ถูกเรียกให้ดำเนินการบล็อกที่ส่งผ่านสำหรับทุกรายการในอาร์เรย์ โปรดทราบว่าเมธอดนี้ไม่มีอาร์กิวเมนต์ เนื่องจากบล็อกจะถูกส่งไปยังเมธอดโดยปริยาย
การบล็อกโดยนัยและ yield
คีย์เวิร์ด
ใน Ruby วิธีการสามารถบล็อกโดยปริยายและชัดเจน การบล็อกโดยปริยายทำงานโดยการเรียก yield
คีย์เวิร์ดในเมธอด yield
คีย์เวิร์ดเป็นพิเศษ ค้นหาและเรียกบล็อกที่ส่งผ่าน ดังนั้นคุณไม่จำเป็นต้องเพิ่มบล็อกในรายการอาร์กิวเมนต์ที่วิธีการยอมรับ
เนื่องจาก Ruby อนุญาตให้มีการบล็อกโดยปริยาย คุณจึงสามารถเรียกวิธีการทั้งหมดที่มีบล็อกได้ ถ้าไม่เรียก yield
บล็อกจะถูกละเว้น
irb> "foo bar baz".split { p "block!" }
=> ["foo", "bar", "baz"]
หากเมธอดที่เรียก ไม่ Yield พบบล็อกที่ส่งผ่านและเรียกด้วยอาร์กิวเมนต์ใดๆ ที่ส่งผ่านไปยัง yield
คีย์เวิร์ด
def each
return to_enum(:each) unless block_given?
i = 0
while i < size
yield at(i)
i += 1
end
end
ตัวอย่างนี้ส่งคืนอินสแตนซ์ของ Enumerator
เว้นแต่จะได้รับบล็อก
yield
และ block_given?
คำหลักค้นหาบล็อกในขอบเขตปัจจุบัน วิธีนี้อนุญาตให้ส่งบล็อกโดยปริยาย แต่ป้องกันไม่ให้โค้ดเข้าถึงบล็อกโดยตรง เนื่องจากไม่ได้จัดเก็บไว้ในตัวแปร
การบล็อกอย่างชัดแจ้ง
เราสามารถยอมรับการบล็อกในวิธีการได้อย่างชัดเจนโดยเพิ่มเป็นอาร์กิวเมนต์โดยใช้พารามิเตอร์เครื่องหมายและ (ปกติเรียกว่า &block
). เนื่องจากตอนนี้บล็อกมีความชัดเจน เราจึงสามารถใช้ #call
เมธอดโดยตรงบนวัตถุผลลัพธ์แทนที่จะอาศัย yield
.
&block
อาร์กิวเมนต์ไม่ใช่อาร์กิวเมนต์ที่เหมาะสม ดังนั้นการเรียกเมธอดนี้ด้วยอย่างอื่นที่ไม่ใช่บล็อกจะสร้าง ArgumentError
.
def each_explicit(&block)
return to_enum(:each) unless block
i = 0
while i < size
block.call at(i)
i += 1
end
end
เมื่อบล็อกถูกส่งผ่านแบบนี้และเก็บไว้ในตัวแปร บล็อกนั้นจะถูกแปลงเป็น proc . โดยอัตโนมัติ .
ขั้นตอนการทำงาน
"proc" คือตัวอย่างของ Proc
คลาสซึ่งมีโค้ดบล็อกที่จะดำเนินการและสามารถเก็บไว้ในตัวแปรได้ ในการสร้าง proc คุณเรียก Proc.new
และส่งผ่านบล็อก
proc = Proc.new { |n| puts "#{n}!" }
เนื่องจาก proc สามารถเก็บไว้ในตัวแปรได้ จึงสามารถส่งผ่านไปยังเมธอดได้เหมือนกับอาร์กิวเมนต์ปกติ ในกรณีนั้น เราจะไม่ใช้เครื่องหมายแอมเพอร์แซนด์ เนื่องจากมีการส่งต่อ proc อย่างชัดเจน
def run_proc_with_random_number(proc)
proc.call(random)
end
proc = Proc.new { |n| puts "#{n}!" }
run_proc_with_random_number(proc)
แทนที่จะสร้าง proc แล้วส่งต่อไปยังเมธอด คุณสามารถใช้ไวยากรณ์พารามิเตอร์เครื่องหมายแอมเพอร์แซนด์ของ Ruby ที่เราเห็นก่อนหน้านี้และใช้บล็อกแทนได้
def run_proc_with_random_number(&proc)
proc.call(random)
end
run_proc_with_random_number { |n| puts "#{n}!" }
สังเกตเครื่องหมายและที่เพิ่มไปยังอาร์กิวเมนต์ในเมธอด การดำเนินการนี้จะแปลงบล็อกที่ส่งผ่านเป็นอ็อบเจ็กต์ proc และเก็บไว้ในตัวแปรในขอบเขตเมธอด
เคล็ดลับ :แม้ว่าจะมีประโยชน์ที่จะมี proc ในเมธอดในบางสถานการณ์ การแปลงบล็อกเป็น proc จะทำให้เกิดประสิทธิภาพ เมื่อใดก็ตามที่เป็นไปได้ ให้ใช้การบล็อกโดยนัยแทน
#to_proc
สัญลักษณ์ แฮช และเมธอดสามารถแปลงเป็น proc ได้โดยใช้ #to_proc
วิธีการ การใช้สิ่งนี้บ่อยครั้งคือการส่งต่อ proc ที่สร้างจากสัญลักษณ์ไปยังเมธอด
[1,2,3].map(&:to_s)
[1,2,3].map {|i| i.to_s }
[1,2,3].map {|i| i.send(:to_s) }
ตัวอย่างนี้แสดงวิธีการเรียก #to_s
. ที่เทียบเท่ากัน 3 วิธี ในแต่ละองค์ประกอบของอาร์เรย์ ในอันแรก สัญลักษณ์ที่นำหน้าด้วยเครื่องหมายและจะถูกส่งต่อ ซึ่งจะแปลงเป็น proc โดยอัตโนมัติโดยเรียก #to_proc
กระบวนการ. สองรายการสุดท้ายแสดงให้เห็นว่า proc นั้นเป็นอย่างไร
class Symbol
def to_proc
Proc.new { |i| i.send(self) }
end
end
แม้ว่านี่จะเป็นเพียงตัวอย่างแบบง่าย แต่การใช้งาน Symbol#to_proc
แสดงให้เห็นว่าเกิดอะไรขึ้นภายใต้ประทุน เมธอดส่งคืน proc ซึ่งรับอาร์กิวเมนต์หนึ่งตัวแล้วส่ง self
กับมัน ตั้งแต่ self
เป็นสัญลักษณ์ในบริบทนี้ โดยเรียก Integer#to_s
วิธีการ
แลมบ์ดาส
แลมบ์ดาเป็นปัจจัยสำคัญที่มีปัจจัยแตกต่างบางประการ พวกเขาเป็นเหมือนวิธีการ "ปกติ" ในสองวิธี:พวกเขาบังคับใช้จำนวนอาร์กิวเมนต์ที่ส่งผ่านเมื่อมีการเรียกและใช้การส่งคืน "ปกติ"
เมื่อเรียกแลมบ์ดาที่คาดหวังอาร์กิวเมนต์โดยไม่มีอาร์กิวเมนต์ หรือหากคุณส่งอาร์กิวเมนต์ไปยังแลมบ์ดาที่ไม่คาดหมาย Ruby จะเพิ่ม ArgumentError
.
irb> lambda (a) { a }.call
ArgumentError: wrong number of arguments (given 0, expected 1)
from (irb):8:in `block in irb_binding'
from (irb):8
from /Users/jeff/.asdf/installs/ruby/2.3.0/bin/irb:11:in `<main>'
นอกจากนี้แลมบ์ดายังปฏิบัติต่อคีย์เวิร์ด return ในลักษณะเดียวกับเมธอด เมื่อเรียกใช้ proc โปรแกรมจะควบคุมบล็อกโค้ดใน proc ดังนั้น ถ้า proc กลับมา ขอบเขตปัจจุบันจะกลับมา หาก proc ถูกเรียกใช้ภายในฟังก์ชันและเรียก return
, ฟังก์ชันจะกลับทันทีเช่นกัน
def return_from_proc
a = Proc.new { return 10 }.call
puts "This will never be printed."
end
ฟังก์ชันนี้จะให้การควบคุมกับ proc ดังนั้นเมื่อกลับมา ฟังก์ชันจะส่งคืน การเรียกใช้ฟังก์ชันในตัวอย่างนี้จะไม่พิมพ์ผลลัพธ์และคืนค่า 10
def return_from_lambda
a = lambda { return 10 }.call
puts "The lambda returned #{a}, and this will be printed."
end
เมื่อใช้แลมบ์ดา จะ จะพิมพ์ กำลังโทร return
ในแลมบ์ดาจะมีพฤติกรรมเหมือนโทรกลับ return
ในวิธีการ ดังนั้น a
ตัวแปรถูกเติมด้วย 10
และบรรทัดจะถูกพิมพ์ไปที่คอนโซล
บล็อก โปรค และแลมบ์ดา
ตอนนี้เราได้เข้าไปในทั้งสองบล็อก procs และ lambdas แล้ว เรามาย่อและสรุปการเปรียบเทียบกัน
- มีการใช้บล็อคอย่างกว้างขวางใน Ruby เพื่อส่งบิตของโค้ดไปยังฟังก์ชันต่างๆ โดยใช้
yield
คีย์เวิร์ด บล็อกสามารถส่งผ่านโดยปริยายโดยไม่ต้องแปลงเป็น proc - เมื่อใช้พารามิเตอร์ที่นำหน้าด้วยเครื่องหมายแอมเพอร์แซนด์ การส่งผ่านบล็อกไปยังเมธอดจะส่งผลให้เกิด proc ในบริบทของเมธอด Procs ทำงานเหมือนบล็อก แต่สามารถเก็บไว้ในตัวแปรได้
- แลมบ์ดาคือ procs ที่ทำงานเหมือนเมธอด ซึ่งหมายความว่าพวกมันบังคับใช้ arity และส่งคืนเป็นเมธอด แทนที่จะอยู่ในขอบเขตพาเรนต์
นี่เป็นการสรุปลักษณะการปิดของเราใน Ruby มีอะไรอีกมากมายให้เรียนรู้เกี่ยวกับการปิด เช่น ขอบเขตคำศัพท์และการผูก แต่เราจะเก็บไว้สำหรับตอนต่อไป ในระหว่างนี้ โปรดแจ้งให้เราทราบว่าคุณต้องการอ่านเกี่ยวกับอะไรในอนาคตของ Ruby Magic การปิดหรืออื่นๆ ที่ @AppSignal