2009-09-16 41 views
7

Câu hỏi này không phải là về cách sử dụng Enumerators trong Ruby 1.9.1 mà đúng hơn là tôi tò mò về cách chúng hoạt động. Dưới đây là một số mã:Làm thế nào để Enumerators làm việc trong Ruby 1.9.1?

class Bunk 
    def initialize 
    @h = [*1..100] 
    end 

    def each 
    if !block_given? 
     enum_for(:each) 
    else 
     0.upto(@h.length) { |i| 
     yield @h[i] 
     } 
    end 
    end 
end 

Trong đoạn mã trên tôi có thể sử dụng e = Bunk.new.each, và sau đó e.next, e.next để có được mỗi phần tử liên tiếp, nhưng làm thế nào chính xác là nó đình chỉ thi công và sau đó nối lại tại đúng chỗ?

Tôi biết rằng nếu sản lượng trong 0.upto được thay thế bằng Fiber.yield thì thật dễ hiểu, nhưng đó không phải là trường hợp ở đây. Nó là một đồng bằng cũ yield, vì vậy nó hoạt động như thế nào?

Tôi đã xem xét điều tra viên nhưng nó rất khó hiểu đối với tôi. Có lẽ ai đó có thể cung cấp một thực hiện trong Ruby, bằng cách sử dụng sợi, không 1.8.6 phong cách tiếp tục dựa trên điều tra, mà làm cho nó tất cả rõ ràng?

Trả lời

12

Dưới đây là một điều tra viên ruby ​​đơn giản có sử dụng sợi và cần khá nhiều hoạt động giống như bản gốc:

class MyEnumerator 
    include Enumerable 

    def initialize(obj, iterator_method) 
    @f = Fiber.new do 
     obj.send(iterator_method) do |*args| 
     Fiber.yield(*args) 
     end 
     raise StopIteration 
    end 
    end 

    def next 
    @f.resume 
    end 

    def each 
    loop do 
     yield self.next 
    end 
    rescue StopIteration 
    self 
    end 
end 

Và trước khi ai đó phàn nàn về trường hợp ngoại lệ như kiểm soát dòng chảy: Các Enumerator thực tăng StopIteration ở cuối, quá, vì vậy Tôi vừa mô phỏng hành vi ban đầu.

Cách sử dụng:

>> enum = MyEnumerator.new([1,2,3,4], :each_with_index) 
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc> 
>> enum.next 
=> [1, 0] 
>> enum.next 
=> [2, 1] 
>> enum.to_a 
=> [[3, 2], [4, 3]] 
4

Thực tế, trong mệnh đề e = Bunk.new.each mệnh đề khác của bạn không được thực thi ban đầu. Thay vào đó, mệnh đề 'if! Block_given' thực thi và trả về một đối tượng liệt kê. Đối tượng liệt kê giữ một đối tượng sợi bên trong. (Ít nhất đó là những gì nó trông giống như trong enumerator.c)

Khi bạn gọi e.each nó đang gọi một phương pháp trên một điều tra viên sử dụng một sợi bên trong để theo dõi bối cảnh thực hiện của nó. Phương thức này gọi phương thức Bunk.each bằng cách sử dụng bối cảnh thực thi xơ. Cuộc gọi Bunk.each ở đây thực hiện mệnh đề else và mang lại giá trị.

Tôi không biết năng suất được triển khai như thế nào hoặc cách chất xơ theo dõi ngữ cảnh thực thi. Tôi chưa xem mã đó. Hầu hết tất cả phép điều tra và phép thuật sợi được thực hiện trong C.

Bạn có thực sự hỏi cách thực hiện xơ và năng suất không? Bạn đang tìm mức chi tiết nào?

Nếu tôi rời khỏi cơ sở, vui lòng sửa tôi.

+0

cảm ơn câu trả lời của bạn. vâng tôi yêu cầu khá nhiều chi tiết về điều này. cụ thể là tôi muốn biết nếu có thể thực hiện tất cả trong Ruby hay liệu có điều gì đó lén lút xảy ra trong C không thể xảy ra trong Ruby. Nếu có thể thực hiện nó hoàn toàn trong Ruby, tôi rất muốn xem mã! :) – horseyguy

1

Như các áp phích khác lưu ý, tôi tin rằng nó tạo ra riêng "sợi" của nó [trong 1,9]. Trong 1.8.7 (hoặc 1.8.6 nếu bạn sử dụng đá quý backports) bằng cách nào đó hoặc khác nó làm điều tương tự (có lẽ bởi vì tất cả các chủ đề trong 1,8 là tương đương với sợi, nó chỉ sử dụng chúng?)

Như vậy trong 1.9 và 1.8.x, nếu bạn chuỗi nhiều trong số họ cùng nhau a.each_line.map.each_with_index {}

Nó thực sự chảy qua mà toàn bộ chuỗi với mỗi dòng, giống như một ống trên dòng lệnh

http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html

HTH.

+1

đây là mô tả chi tiết tuyệt vời http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack

1

Tôi nghĩ điều này sẽ chính xác hơn.Gọi mỗi điều tra viên phải giống như gọi phương thức lặp ban đầu. Vì vậy, tôi sẽ hơi thay đổi giải pháp ban đầu cho điều này:

class MyEnumerator 
    include Enumerable 

    def initialize(obj, iterator_method) 
    @f = Fiber.new do 
     @result = obj.send(iterator_method) do |*args| 
     Fiber.yield(*args) 
     end 
     raise StopIteration 
    end 
    end 

    def next(result) 
    @f.resume result 
    end 

    def each 
    result = nil 
    loop do 
     result = yield(self.next(result)) 
    end 
    @result 
    end 
end 
Các vấn đề liên quan