Tôi đang cố gắng hiểu các khối yield
và cách chúng hoạt động trong Ruby.
Làm thế nào được yield
sử dụng? Nhiều ứng dụng Rails tôi đã xem xét sử dụng yield
một cách kỳ lạ.
Ai đó có thể giải thích cho tôi hoặc chỉ cho tôi nơi để đi để hiểu họ?
Tôi đang cố gắng hiểu các khối yield
và cách chúng hoạt động trong Ruby.
Làm thế nào được yield
sử dụng? Nhiều ứng dụng Rails tôi đã xem xét sử dụng yield
một cách kỳ lạ.
Ai đó có thể giải thích cho tôi hoặc chỉ cho tôi nơi để đi để hiểu họ?
Câu trả lời:
Vâng, lúc đầu hơi khó hiểu.
Trong Ruby, các phương thức có thể nhận được một khối mã để thực hiện các phân đoạn mã tùy ý.
Khi một phương thức mong đợi một khối, nó gọi nó bằng cách gọi yield
hàm.
Điều này rất tiện dụng, ví dụ, lặp đi lặp lại qua một danh sách hoặc để cung cấp một thuật toán tùy chỉnh.
Lấy ví dụ sau:
Tôi sẽ định nghĩa một Person
lớp được khởi tạo với một tên và cung cấp một do_with_name
phương thức mà khi được gọi, sẽ chỉ truyền name
thuộc tính cho khối nhận được.
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
Điều này sẽ cho phép chúng ta gọi phương thức đó và vượt qua một khối mã tùy ý.
Ví dụ, để in tên, chúng tôi sẽ làm:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
Sẽ in:
Hey, his name is Oscar
Lưu ý, khối nhận, như một tham số, một biến được gọi name
(NB bạn có thể gọi biến này là bất cứ thứ gì bạn thích, nhưng sẽ rất hợp lý khi gọi nó name
). Khi mã gọi yield
nó sẽ điền tham số này với giá trị là @name
.
yield( @name )
Chúng tôi có thể cung cấp một khối khác để thực hiện một hành động khác. Ví dụ: đảo ngược tên:
#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"
Chúng tôi đã sử dụng chính xác cùng một phương thức ( do_with_name
) - nó chỉ là một khối khác nhau.
Ví dụ này là tầm thường. Các cách sử dụng thú vị hơn là lọc tất cả các thành phần trong một mảng:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
Hoặc, chúng tôi cũng có thể cung cấp thuật toán sắp xếp tùy chỉnh, ví dụ dựa trên kích thước chuỗi:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Tôi hy vọng điều này sẽ giúp bạn hiểu nó tốt hơn.
BTW, nếu khối là tùy chọn, bạn nên gọi nó như sau:
yield(value) if block_given?
Nếu không phải là tùy chọn, chỉ cần gọi nó.
BIÊN TẬP
@hmak đã tạo một repl.it cho các ví dụ sau: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
nếu the_name = ""
"Oscar"
(không rõ ràng trong câu trả lời)
person.do_with_name {|string| yield string, something_else }
Trong Ruby, các phương thức có thể kiểm tra xem liệu chúng có được gọi theo cách mà một khối được cung cấp ngoài các đối số bình thường hay không. Thông thường, điều này được thực hiện bằng block_given?
phương thức nhưng bạn cũng có thể gọi khối là một Proc rõ ràng bằng cách thêm tiền tố vào dấu và ( &
trước tên đối số cuối cùng.
Nếu một phương thức được gọi với một khối thì phương thức đó có thể yield
điều khiển khối (gọi khối) với một số đối số, nếu cần. Xem xét phương pháp ví dụ này chứng minh:
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 =)
Hoặc, sử dụng cú pháp đối số khối đặc biệt:
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 =)
Rất có khả năng ai đó sẽ cung cấp một câu trả lời thực sự chi tiết ở đây, nhưng tôi luôn thấy bài đăng này của Robert Sosinski là một lời giải thích tuyệt vời về sự tinh tế giữa các khối, procs & lambdas.
Tôi nên thêm rằng tôi tin rằng bài đăng tôi đang liên kết là dành riêng cho ruby 1.8. Một số thứ đã thay đổi trong ruby 1.9, chẳng hạn như các biến khối là cục bộ của khối. Trong 1.8, bạn sẽ nhận được một cái gì đó như sau:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Trong khi 1.9 sẽ cung cấp cho bạn:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
Tôi không có 1.9 trên máy này vì vậy ở trên có thể có lỗi trong đó.
Tôi muốn sắp xếp thêm lý do tại sao bạn sẽ làm mọi thứ theo cách đó cho câu trả lời tuyệt vời.
Không biết bạn đến từ ngôn ngữ nào, nhưng giả sử đó là ngôn ngữ tĩnh, loại thứ này sẽ trông quen thuộc. Đây là cách bạn đọc một tập tin trong 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();
}
}
}
Bỏ qua tất cả các chuỗi xích, Ý tưởng là thế này
Đây là cách bạn làm điều đó trong ruby
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Hoang dã khác nhau. Phá vỡ cái này
Ở đây, thay vì xử lý bước một và hai, về cơ bản, bạn ủy thác điều đó cho một lớp khác. Như bạn có thể thấy, điều đó làm giảm đáng kể số lượng mã bạn phải viết, giúp mọi thứ dễ đọc hơn và giảm khả năng những thứ như rò rỉ bộ nhớ hoặc khóa tệp không bị xóa.
Bây giờ, nó không giống như bạn không thể làm một cái gì đó tương tự trong java, trên thực tế, mọi người đã làm điều đó trong nhiều thập kỷ nay. Nó được gọi là mẫu Chiến lược . Sự khác biệt là không có khối, đối với một cái gì đó đơn giản như ví dụ về tệp, chiến lược trở nên quá mức do số lượng lớp và phương thức bạn cần viết. Với các khối, đó là một cách đơn giản và thanh lịch để làm điều đó, nó không có nghĩa gì khi KHÔNG cấu trúc mã của bạn theo cách đó.
Đây không phải là cách duy nhất được sử dụng, nhưng các khối khác (như mẫu Builder, mà bạn có thể thấy trong form_for api trong đường ray) tương tự nhau đến mức rõ ràng những gì đang diễn ra khi bạn quấn đầu xung quanh điều này. Khi bạn thấy các khối, thường an toàn khi giả định rằng cuộc gọi phương thức là điều bạn muốn làm và khối đó mô tả cách bạn muốn thực hiện.
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
và cười thậm chí còn khó hơn với những kẻ Java.
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(cộng với không có vấn đề về bộ nhớ)
Tôi thấy bài viết này rất hữu ích. Cụ thể, ví dụ sau:
#!/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
cái nào sẽ cho đầu ra sau:
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
Vì vậy, về cơ bản mỗi khi một cuộc gọi được thực hiện cho yield
ruby sẽ chạy mã trong do
khối hoặc bên trong {}
. Nếu một tham số được cung cấp cho yield
thì điều này sẽ được cung cấp dưới dạng tham số cho do
khối.
Đối với tôi, đây là lần đầu tiên tôi thực sự hiểu những gì các do
khối đang làm. Về cơ bản, đây là cách để hàm cung cấp quyền truy cập vào các cấu trúc dữ liệu nội bộ, là cách lặp hoặc để cấu hình chức năng.
Vì vậy, khi trong đường ray bạn viết như sau:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Điều này sẽ chạy respond_to
chức năng mang lại do
khối với format
tham số (nội bộ) . Sau đó, bạn gọi .html
hàm trên biến nội bộ này, từ đó tạo ra khối mã để chạy render
lệnh. Lưu ý rằng .html
sẽ chỉ mang lại nếu nó là định dạng tệp được yêu cầu. (tính kỹ thuật: các chức năng này thực sự sử dụng block.call
không phải yield
như bạn có thể thấy từ nguồn nhưng chức năng về cơ bản là giống nhau, xem câu hỏi này để thảo luận.) Điều này cung cấp một cách để chức năng thực hiện một số khởi tạo sau đó lấy đầu vào từ mã gọi và sau đó tiến hành xử lý nếu được yêu cầu.
Hoặc nói theo một cách khác, nó tương tự như một hàm lấy một hàm ẩn danh làm đối số và sau đó gọi nó trong javascript.
Trong Ruby, một khối về cơ bản là một đoạn mã có thể được truyền vào và thực thi bằng bất kỳ phương thức nào. Các khối luôn được sử dụng với các phương thức, thường cung cấp dữ liệu cho chúng (dưới dạng đối số).
Các khối được sử dụng rộng rãi trong đá quý Ruby (bao gồm cả Rails) và trong mã Ruby được viết tốt. Chúng không phải là đối tượng, do đó không thể được gán cho các biến.
Một khối là một đoạn mã được bao quanh bởi {} hoặc do..end. Theo quy ước, cú pháp dấu ngoặc nhọn nên được sử dụng cho các khối đơn dòng và cú pháp do..end nên được sử dụng cho các khối nhiều dòng.
{ # This is a single line block }
do
# This is a multi-line block
end
Bất kỳ phương thức nào cũng có thể nhận được một khối làm đối số ngầm. Một khối được thực thi bởi câu lệnh lợi suất trong một phương thức. Cú pháp cơ bản là:
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.
Khi đạt được câu lệnh năng suất, phương thức thiền mang lại điều khiển cho khối, mã trong khối được thực thi và điều khiển được trả về phương thức, tiếp tục thực hiện ngay sau câu lệnh lợi suất.
Khi một phương thức chứa một tuyên bố lợi suất, nó sẽ nhận được một khối tại thời điểm gọi. Nếu một khối không được cung cấp, một ngoại lệ sẽ được đưa ra sau khi đạt được tuyên bố lợi tức. Chúng tôi có thể làm cho khối tùy chọn và tránh ngoại lệ được nêu ra:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
Không thể truyền nhiều khối cho một phương thức. Mỗi phương pháp chỉ có thể nhận được một khối.
Xem thêm tại: http://www.zenruby.info/2016/04/int sinhtion-to-blocks-in-ruby.html
Đôi khi tôi sử dụng "năng suất" như thế này:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
phải không thực hiện một số tác vụ nếu người dùng không cần. Bạn nên giải thích về bạn mặc dù ...
Có hai điểm tôi muốn thực hiện về năng suất ở đây. Đầu tiên, trong khi rất nhiều câu trả lời ở đây nói về các cách khác nhau để chuyển một khối sang một phương thức sử dụng năng suất, thì chúng ta cũng hãy nói về luồng điều khiển. Điều này đặc biệt có liên quan vì bạn có thể mang lại NHIỀU lần cho một khối. Hãy xem một ví dụ:
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
Khi mỗi phương thức được gọi, nó thực thi từng dòng một. Bây giờ khi chúng ta đến khối 3.times, khối này sẽ được gọi 3 lần. Mỗi lần nó gọi năng suất. Sản lượng đó được liên kết với khối liên kết với phương thức được gọi là mỗi phương thức. Điều quan trọng cần lưu ý là mỗi lần sản lượng được gọi, nó sẽ trả lại quyền điều khiển cho khối của từng phương thức trong mã máy khách. Khi khối được thực hiện xong, nó sẽ quay trở lại khối 3.times. Và điều này xảy ra 3 lần. Vì vậy, khối trong mã máy khách được gọi trong 3 lần riêng biệt do năng suất được gọi rõ ràng là 3 lần riêng biệt.
Điểm thứ hai của tôi là về enum_for và năng suất. enum_for khởi tạo lớp Enumerator và đối tượng Enumerator này cũng đáp ứng với năng suất.
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"
Vì vậy, hãy chú ý mỗi khi chúng ta gọi các loại với trình vòng lặp bên ngoài, nó sẽ chỉ gọi năng suất một lần. Lần sau chúng ta gọi nó, nó sẽ gọi sản lượng tiếp theo và cứ thế.
Có một mẩu tin thú vị liên quan đến enum_for. Các tài liệu trực tuyến nêu sau:
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
Nếu bạn không chỉ định một biểu tượng làm đối số cho enum_for, ruby sẽ nối trình liệt kê vào từng phương thức của người nhận. Một số lớp không có mỗi phương thức, như lớp String.
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
Do đó, trong trường hợp một số đối tượng được gọi bằng enum_for, bạn phải rõ ràng về phương thức liệt kê của bạn.
Yield có thể được sử dụng như một khối không tên để trả về một giá trị trong phương thức. Hãy xem xét các mã sau đây:
Def Up(anarg)
yield(anarg)
end
Bạn có thể tạo một phương thức "Lên" được gán một đối số. Bây giờ bạn có thể gán đối số này để mang lại kết quả sẽ gọi và thực thi một khối liên quan. Bạn có thể gán khối sau danh sách tham số.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Khi phương thức Up gọi năng suất, với một đối số, nó được chuyển đến biến khối để xử lý yêu cầu.