ฉันพยายามทำความเข้าใจกับบล็อกyield
และวิธีการทำงานของ Ruby
วิธีการyield
ใช้งานอย่างไร แอพพลิเคชั่นของ Rails หลายตัวที่ฉันเคยใช้yield
ดูแปลก ๆ
มีใครอธิบายให้ฉันฟังหรือแสดงให้ฉันดูว่าจะไปทำความเข้าใจกับพวกเขาได้ที่ไหน
ฉันพยายามทำความเข้าใจกับบล็อกyield
และวิธีการทำงานของ Ruby
วิธีการyield
ใช้งานอย่างไร แอพพลิเคชั่นของ Rails หลายตัวที่ฉันเคยใช้yield
ดูแปลก ๆ
มีใครอธิบายให้ฉันฟังหรือแสดงให้ฉันดูว่าจะไปทำความเข้าใจกับพวกเขาได้ที่ไหน
คำตอบ:
ใช่มันเป็นเรื่องที่น่าสับสนในตอนแรก
ในทับทิมวิธีการอาจได้รับการบล็อกรหัสเพื่อดำเนินการส่วนรหัสโดยพลการ
เมื่อเมธอดคาดว่าบล็อกมันจะเรียกใช้โดยการเรียกyield
ฟังก์ชัน
สิ่งนี้มีประโยชน์มากเช่นการวนซ้ำรายการหรือเพื่อให้อัลกอริทึมแบบกำหนดเอง
นำตัวอย่างต่อไปนี้:
ฉันจะกำหนดPerson
ชั้นเรียนเริ่มต้นด้วยชื่อและให้do_with_name
วิธีการที่เมื่อเรียกก็จะส่งname
แอตทริบิวต์ไปยังบล็อกที่ได้รับ
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
สิ่งนี้จะช่วยให้เราสามารถเรียกวิธีการนั้นและผ่านบล็อกรหัสโดยพลการ
ตัวอย่างเช่นในการพิมพ์ชื่อเราจะทำ:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
จะพิมพ์:
Hey, his name is Oscar
สังเกตุบล็อคได้รับเป็นตัวแปรที่เรียกว่าname
(NB คุณสามารถเรียกตัวแปรนี้ได้ทุกอย่างที่คุณต้องการ แต่มันก็เหมาะสมที่จะเรียกมันname
) เมื่อรหัสเรียกมันเติมพารามิเตอร์นี้มีค่าของyield
@name
yield( @name )
เราสามารถให้บล็อกอื่นเพื่อทำการกระทำที่แตกต่าง ตัวอย่างเช่นย้อนกลับชื่อ:
#variable to hold the name reversed
reversed_name = ""
#invoke the method passing a different block
person.do_with_name do |name|
reversed_name = name.reverse
end
puts reversed_name
=> "racsO"
เราใช้วิธีการเดียวกันอย่างแน่นอน ( do_with_name
) - มันเป็นเพียงบล็อกที่แตกต่างกัน
ตัวอย่างนี้เล็กน้อย ประเพณีที่น่าสนใจกว่านั้นคือการกรององค์ประกอบทั้งหมดในอาเรย์:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
หรือเราสามารถจัดเตรียมอัลกอริทึมการเรียงลำดับแบบกำหนดเองได้เช่นตามขนาดสตริง:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
ฉันหวังว่านี่จะช่วยให้คุณเข้าใจได้ดีขึ้น
BTW ถ้าบล็อกเป็นตัวเลือกคุณควรเรียกว่า:
yield(value) if block_given?
หากไม่ใช่ตัวเลือกให้เรียกใช้
แก้ไข
@hmak สร้าง repl.it สำหรับตัวอย่างเหล่านี้: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
อย่างไร the_name = ""
"Oscar"
(ไม่ชัดเจนมากในคำตอบ)
person.do_with_name {|string| yield string, something_else }
ใน Ruby เมธอดสามารถตรวจสอบเพื่อดูว่าถูกเรียกในลักษณะที่บล็อกถูกจัดเตรียมเพิ่มเติมจากอาร์กิวเมนต์ปกติหรือไม่ โดยทั่วไปแล้วจะใช้block_given?
วิธีนี้ แต่คุณยังสามารถอ้างถึงบล็อกเป็น Proc อย่างชัดเจนโดยการใส่เครื่องหมายแอมเปอร์แซนด์ ( &
) ก่อนหน้าชื่ออาร์กิวเมนต์สุดท้าย
หากมีการเรียกใช้เมธอดด้วยบล็อกดังนั้นเมธอดสามารถyield
ควบคุมบล็อก (เรียกบล็อก) ด้วยอาร์กิวเมนต์บางตัวหากจำเป็น ลองพิจารณาตัวอย่างวิธีนี้ที่สาธิต:
def foo(x)
puts "OK: called as foo(#{x.inspect})"
yield("A gift from foo!") if block_given?
end
foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)
หรือใช้ไวยากรณ์อาร์กิวเมนต์บล็อกพิเศษ:
def bar(x, &block)
puts "OK: called as bar(#{x.inspect})"
block.call("A gift from bar!") if block
end
bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)
เป็นไปได้มากที่บางคนจะให้คำตอบโดยละเอียดอย่างแท้จริงที่นี่ แต่ฉันพบเสมอว่าโพสต์นี้จาก Robert Sosinski เป็นคำอธิบายที่ดีเกี่ยวกับรายละเอียดปลีกย่อยระหว่างบล็อค procs & lambdas
ฉันควรเพิ่มว่าฉันเชื่อว่าโพสต์ที่ฉันลิงก์ไปนั้นเฉพาะกับทับทิม 1.8 บางสิ่งมีการเปลี่ยนแปลงใน ruby 1.9 เช่นตัวแปรบล็อกที่อยู่ภายในบล็อก ใน 1.8 คุณจะได้รับสิ่งต่อไปนี้:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
ในขณะที่ 1.9 จะให้:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
ฉันไม่มี 1.9 ในเครื่องนี้ดังนั้นข้างต้นอาจมีข้อผิดพลาด
ฉันต้องการเรียงลำดับเพิ่มว่าทำไมคุณจะทำสิ่งต่าง ๆ เพื่อตอบคำถามที่ยอดเยี่ยมแล้ว
ไม่มีความคิดว่าคุณมาจากภาษาใด แต่สมมติว่าเป็นภาษาแบบคงที่สิ่งต่าง ๆ เช่นนี้จะดูคุ้นเคย นี่คือวิธีที่คุณอ่านไฟล์ใน java
public class FileInput {
public static void main(String[] args) {
File file = new File("C:\\MyFile.txt");
FileInputStream fis = null;
BufferedInputStream bis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(file);
// Here BufferedInputStream is added for fast reading.
bis = new BufferedInputStream(fis);
dis = new DataInputStream(bis);
// dis.available() returns 0 if the file does not have more lines.
while (dis.available() != 0) {
// this statement reads the line from the file and print it to
// the console.
System.out.println(dis.readLine());
}
// dispose all the resources after using them.
fis.close();
bis.close();
dis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ไม่สนใจสิ่งที่ผูกมัดสายน้ำทั้งหมดความคิดคือสิ่งนี้
นี่คือวิธีที่คุณทำในทับทิม
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
ต่างกันอย่างดุเดือด ทำลายอันนี้ลง
ที่นี่แทนที่จะจัดการกับขั้นตอนที่หนึ่งและสองคุณจะมอบมันให้กับคลาสอื่น อย่างที่คุณสามารถเห็นได้ว่าจะลดปริมาณของรหัสที่คุณต้องเขียนลงอย่างมากซึ่งทำให้อ่านง่ายขึ้นและลดโอกาสของการรั่วไหลของหน่วยความจำหรือล็อคไฟล์ที่ไม่ได้รับการล้าง
ตอนนี้มันไม่เหมือนกับที่คุณไม่สามารถทำสิ่งที่คล้ายกันในจาวาในความเป็นจริงคนได้ทำมานานหลายสิบปีแล้ว มันเรียกว่ารูปแบบกลยุทธ์ ความแตกต่างคือไม่มีบล็อกสำหรับสิ่งที่เรียบง่ายเช่นตัวอย่างไฟล์กลยุทธ์กลายเป็น overkill เนื่องจากจำนวนคลาสและวิธีการที่คุณต้องเขียน ด้วยบล็อกมันเป็นวิธีที่เรียบง่ายและสง่างามในการทำเช่นนั้นซึ่งมันไม่สมเหตุสมผลที่จะไม่สร้างโครงสร้างโค้ดของคุณในลักษณะนั้น
นี่ไม่ใช่วิธีเดียวที่ใช้ในการบล็อก แต่ส่วนอื่น ๆ (เช่นรูปแบบของตัวสร้างซึ่งคุณสามารถเห็นได้ใน form_for api ในราง) มีลักษณะคล้ายกันมากพอที่จะเห็นได้ชัดว่าเกิดอะไรขึ้นเมื่อคุณล้อมรอบสิ่งนี้ เมื่อคุณเห็นบล็อกมักจะปลอดภัยที่จะถือว่าการเรียกใช้เมธอดเป็นสิ่งที่คุณต้องการทำและบล็อกอธิบายวิธีที่คุณต้องการทำ
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
แล้วหัวเราะให้หนักขึ้นที่พวก Java
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(บวกกับปัญหาหน่วยความจำไม่ได้)
ฉันพบว่าบทความนี้มีประโยชน์มาก โดยเฉพาะอย่างยิ่งตัวอย่างต่อไปนี้:
#!/usr/bin/ruby
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
test do |i|
puts "You are in the block #{i}"
end
ซึ่งควรให้ผลลัพธ์ต่อไปนี้:
You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100
เพื่อเป็นหลักในแต่ละครั้งที่มีการโทรที่เกิดขึ้นกับyield
ทับทิมจะเรียกใช้รหัสในการบล็อกหรือภายในdo
{}
หากมีการระบุพารามิเตอร์ไว้yield
สิ่งนี้จะถูกจัดให้เป็นพารามิเตอร์ของdo
บล็อก
สำหรับฉันนี่เป็นครั้งแรกที่ฉันเข้าใจจริงๆว่าdo
บล็อกกำลังทำอะไร มันเป็นวิธีที่ฟังก์ชั่นให้การเข้าถึงโครงสร้างข้อมูลภายในไม่ว่าจะเป็นการวนซ้ำหรือการกำหนดค่าฟังก์ชั่น
ดังนั้นเมื่ออยู่ในรางคุณเขียนสิ่งต่อไปนี้:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
สิ่งนี้จะเรียกใช้respond_to
ฟังก์ชันที่ให้ผลลัพธ์do
บล็อกด้วยformat
พารามิเตอร์(ภายใน) จากนั้นคุณเรียกใช้.html
ฟังก์ชันบนตัวแปรภายในนี้ซึ่งจะส่งผลให้บล็อกโค้ดเพื่อรันrender
คำสั่ง โปรดทราบว่า.html
จะให้ผลก็ต่อเมื่อเป็นรูปแบบไฟล์ที่ร้องขอ (ด้านเทคนิค: ฟังก์ชั่นเหล่านี้ใช้งานblock.call
ได้จริงอย่างที่ไม่yield
เห็นจากแหล่งที่มาแต่ฟังก์ชั่นนั้นเหมือนกันให้ดูคำถามนี้สำหรับการอภิปราย) นี่เป็นวิธีการที่ฟังก์ชั่นในการเริ่มต้นใช้งาน จากนั้นดำเนินการประมวลผลถ้าจำเป็น
หรือใส่วิธีอื่นมันคล้ายกับฟังก์ชั่นที่ใช้ฟังก์ชั่นที่ไม่ระบุชื่อเป็นอาร์กิวเมนต์แล้วเรียกมันในจาวาสคริปต์
ใน Ruby บล็อกนั้นเป็นกลุ่มของโค้ดที่สามารถส่งผ่านและดำเนินการโดยวิธีใดก็ได้ บล็อกมักจะใช้กับวิธีการซึ่งมักจะฟีดข้อมูลให้กับพวกเขา (เป็นข้อโต้แย้ง)
บล็อกถูกนำมาใช้กันอย่างแพร่หลายในพลอยทับทิม (รวมถึงราง) และในรหัสทับทิมที่เขียนดี มันไม่ใช่วัตถุดังนั้นจึงไม่สามารถกำหนดให้กับตัวแปรได้
บล็อกคือชิ้นส่วนของรหัสที่ล้อมรอบด้วย {} หรือ do..end ตามแบบแผนควรใช้ซิงก์ brace แบบลอนสำหรับบล็อกบรรทัดเดียวและควรใช้ไวยากรณ์ซิงก์ .. สำหรับบล็อกหลายบรรทัด
{ # This is a single line block }
do
# This is a multi-line block
end
วิธีการใด ๆ สามารถรับบล็อกเป็นอาร์กิวเมนต์โดยนัย บล็อกถูกดำเนินการโดยคำสั่งผลผลิตภายในวิธีการ ไวยากรณ์พื้นฐานคือ:
def meditate
print "Today we will practice zazen"
yield # This indicates the method is expecting a block
end
# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }
Output:
Today we will practice zazen for 40 minutes.
เมื่อถึงคำสั่งให้ผลผลิตวิธีการทำสมาธิจะให้การควบคุมกับบล็อกโค้ดภายในบล็อกนั้นจะถูกดำเนินการและการควบคุมจะถูกส่งกลับไปที่เมธอด
เมื่อเมธอดมีข้อความชี้แจงผลตอบแทนคาดว่าจะได้รับบล็อกในเวลาที่โทร หากไม่ได้จัดเตรียมบล็อกไว้จะมีข้อผิดพลาดเกิดขึ้นเมื่อถึงประกาศผลตอบแทน เราสามารถทำให้บล็อกเป็นทางเลือกและหลีกเลี่ยงข้อยกเว้นจากการเพิ่ม:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
ไม่สามารถส่งหลาย ๆ บล็อกไปที่เมธอดได้ แต่ละวิธีสามารถรับได้เพียงหนึ่งบล็อกเท่านั้น
ดูเพิ่มเติมได้ที่: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
บางครั้งฉันใช้ "ผลตอบแทน" เช่นนี้:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
ไม่ต้องทำงานบางอย่างหากผู้ใช้ไม่จำเป็นต้อง คุณควรอธิบายว่า ...
อัตราผลตอบแทนที่จะพูดง่าย ๆ อนุญาตให้วิธีที่คุณสร้างขึ้นเพื่อใช้และบล็อกการโทร คำหลักผลตอบแทนโดยเฉพาะคือจุดที่ 'สิ่ง' ในบล็อกจะดำเนินการ
มีสองจุดที่ฉันต้องการทำเกี่ยวกับผลผลิตที่นี่ ก่อนในขณะที่คำตอบมากมายที่นี่พูดถึงวิธีต่าง ๆ ในการส่งบล็อกไปยังวิธีที่ใช้ผลตอบแทนเรามาพูดถึงการควบคุมกระแส สิ่งนี้มีความเกี่ยวข้องเป็นพิเศษเนื่องจากคุณสามารถให้ผลคูณกับบล็อกได้หลายครั้ง ลองมาดูตัวอย่าง:
class Fruit
attr_accessor :kinds
def initialize
@kinds = %w(orange apple pear banana)
end
def each
puts 'inside each'
3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
end
end
f = Fruit.new
f.each do |kind|
puts 'inside block'
end
=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
เมื่อแต่ละเมธอดถูกเรียกใช้จะเรียกใช้งานทีละบรรทัด ตอนนี้เมื่อเราไปถึงบล็อก 3.times บล็อกนี้จะถูกเรียก 3 ครั้ง ทุกครั้งที่มันก่อให้เกิดผลผลิต ผลผลิตนั้นเชื่อมโยงกับบล็อกที่เกี่ยวข้องกับวิธีการที่เรียกว่าแต่ละวิธี สิ่งสำคัญคือให้สังเกตว่าแต่ละครั้งที่มีการเรียกใช้ผลตอบแทนจะส่งคืนการควบคุมกลับไปยังบล็อกของแต่ละวิธีในรหัสลูกค้า เมื่อบล็อกดำเนินการเสร็จสิ้นบล็อกจะกลับสู่บล็อก 3.times และนี่เกิดขึ้น 3 ครั้ง ดังนั้นบล็อกในรหัสลูกค้าจะถูกเรียกใช้ใน 3 โอกาสที่แยกกันเนื่องจากอัตราผลตอบแทนจะถูกเรียกอย่างชัดเจน 3 ครั้ง
จุดที่สองของฉันเกี่ยวกับ enum_for และผลผลิต enum_for อินสแตนซ์คลาสของตัวแจงนับและวัตถุตัวแจงนับนี้ยังตอบสนองต่อผลผลิต
class Fruit
def initialize
@kinds = %w(orange apple)
end
def kinds
yield @kinds.shift
yield @kinds.shift
end
end
f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
=> "orange"
enum.next
=> "apple"
ดังนั้นให้สังเกตทุกครั้งที่เราเรียกใช้ชนิดด้วยตัววนซ้ำภายนอกมันจะเรียกใช้ผลตอบแทนเพียงครั้งเดียว ครั้งต่อไปที่เราเรียกมันว่ามันจะก่อให้เกิดผลผลิตต่อไปเป็นต้น
มีชิ้นอาหารอันโอชะที่น่าสนใจเกี่ยวกับ enum_for เอกสารออนไลน์ระบุสิ่งต่อไปนี้:
enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.
str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }
# => 120
# => 121
# => 122
หากคุณไม่ระบุสัญลักษณ์เป็นอาร์กิวเมนต์ให้กับ enum_for ทับทิมจะขอให้ตัวแจงนับกับแต่ละวิธีของผู้รับ บางคลาสไม่มีวิธีแต่ละวิธีเช่นคลาส String
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
ดังนั้นในกรณีของวัตถุบางอย่างที่เรียกใช้ด้วย enum_for คุณต้องชัดเจนว่าวิธีการแจกแจงของคุณคืออะไร
สามารถใช้Yieldเป็นบล็อกแบบไม่ระบุชื่อเพื่อส่งคืนค่าในเมธอด พิจารณารหัสต่อไปนี้:
Def Up(anarg)
yield(anarg)
end
คุณสามารถสร้างวิธีการ "ขึ้น" ซึ่งได้รับการกำหนดหนึ่งข้อโต้แย้ง ตอนนี้คุณสามารถกำหนดอาร์กิวเมนต์นี้เพื่อให้ได้ผลลัพธ์ซึ่งจะเรียกและดำเนินการบล็อกที่เกี่ยวข้อง คุณสามารถกำหนดบล็อกหลังรายการพารามิเตอร์
Up("Here is a string"){|x| x.reverse!; puts(x)}
เมื่อการเรียกใช้เมธอด Up พร้อมด้วยอาร์กิวเมนต์จะถูกส่งไปยังตัวแปรบล็อกเพื่อประมวลผลคำขอ