2013-04-12 16 views
5

Enumerable#lazy dựa vào số bạn cung cấp phương thức #each. Nếu số đếm của bạn không có phương thức #each, bạn không thể sử dụng #lazy. Bây giờ Kernel#enum_for#to_enum cung cấp sự linh hoạt để xác định một phương pháp liệt kê khác hơn #each:Cách tốt nhất để trả lại một điều tra viên là gì? Lười biếng khi lớp học của bạn không định nghĩa #each?

Kernel#enum_for(method = :each, *args) 

Nhưng #enum_for và bạn bè luôn luôn xây dựng đơn giản (không lười biếng) điều tra viên, không bao giờ Enumerator::Lazy.

Tôi thấy rằng Enumerator trong Ruby 1.9.3 cung cấp hình thức tương tự như này # mới:

Enumerator#new(obj, method = :each, *args) 

Đáng tiếc là nhà xây dựng đã được loại bỏ hoàn toàn trong Ruby 2.0. Ngoài ra tôi không nghĩ rằng nó đã có sẵn ở tất cả trên Enumerator::Lazy. Vì vậy, có vẻ như với tôi rằng nếu tôi có một lớp học với một phương pháp tôi muốn trả lại một điều tra lười biếng cho, nếu lớp đó không có #each thì tôi phải xác định một số lớp trợ giúp mà xác định #each.

Ví dụ: tôi có lớp học Calendar. Nó không thực sự có ý nghĩa đối với tôi để cung cấp để liệt kê mọi ngày từ đầu mọi thời đại. An #each sẽ là vô dụng. Thay vào đó tôi đưa ra một phương pháp mà liệt kê (uể oải) từ ngày bắt đầu:

class Calendar 
    ... 
    def each_from(first) 
     if block_given? 
     loop do 
      yield first if include?(first) 
      first += step 
     end 
     else 
     EachFrom.new(self, first).lazy 
     end 
    end 
    end 

Và đó lớp EachFrom trông như thế này:

class EachFrom 
    include Enumerable 
    def initialize(cal, first) 
    @cal = cal 
    @first = first 
    end 
    def each 
    @cal.each_from(@first) do |yielder, *vals| 
     yield yielder, *vals 
    end 
    end 
end 

Nó hoạt động nhưng nó cảm thấy nặng nề. Có lẽ tôi nên phân lớp Enumerator::Lazy và xác định một hàm tạo như thế không được dùng nữa từ Enumerator. Bạn nghĩ sao?

Trả lời

7

Tôi nghĩ bạn nên trở lại bình thường Enumerator sử dụng to_enum:

class Calendar 
    # ... 
    def each_from(first) 
    return to_enum(:each_from, first) unless block_given? 
    loop do 
     yield first if include?(first) 
     first += step 
    end 
    end 
end 

Đây là điều mà hầu hết rubyists mong đợi. Mặc dù nó là một vô hạn Enumerable, nó vẫn còn có thể sử dụng, ví dụ:

Calendar.new.each_from(1.year.from_now).first(10) # => [...first ten dates...] 

Nếu họ thực sự cần một điều tra viên lười biếng, họ có thể gọi lazy mình:

Calendar.new.each_from(1.year.from_now) 
    .lazy 
    .map{...} 
    .take_while{...} 

Nếu bạn thực sự muốn trả lại một điều tra viên lười biếng, bạn có thể gọi lazy từ phương thức của bạn:

# ... 
    def each_from(first) 
    return to_enum(:each_from, first).lazy unless block_given? 
    #... 

Tôi sẽ không khuyên bạn nên nó mặc dù, vì nó sẽ là bất ngờ (IMO), có thể là một overkill và sẽ ít performant.

Cuối cùng, có một vài quan niệm sai lầm trong câu hỏi của bạn:

  • Tất cả các phương pháp Enumerable giả một each, không chỉ lazy.

  • Bạn có thể xác định phương thức each yêu cầu tham số nếu bạn muốn và bao gồm Enumerable. Hầu hết các phương pháp của Enumerable sẽ không hoạt động, nhưng each_with_index và một vài người khác sẽ chuyển tiếp các đối số để chúng có thể sử dụng được ngay lập tức.

  • Enumerator.new không có khối nào bị mất vì to_enum là thứ bạn nên sử dụng. Lưu ý rằng biểu mẫu khối vẫn còn. Ngoài ra còn có một hàm tạo cho Lazy, nhưng nó có nghĩa là bắt đầu từ một hiện tại Enumerable.

  • Bạn tuyên bố rằng to_enum không bao giờ tạo bảng liệt kê lười biếng, nhưng điều đó không hoàn toàn đúng. Enumerator::Lazy#to_enum chuyên trả lại một điều tra viên lười biếng. Bất kỳ phương thức người dùng nào trên Enumerable gọi số to_enum sẽ khiến người điều tra lười biếng lười biếng.

+1

Bạn vừa mới thổi tâm trí của tôi là Marc-André. Mã của tôi chỉ từ idiotic đến thành ngữ. Tôi không hiểu rằng Ruby muốn chúng tôi luôn luôn lưu lượng truy cập trong Enumerators và không Enumerator :: Lazy. Bất cứ nơi nào chúng ta cần một cái gì đó để được lười biếng, chúng tôi yêu cầu điều tra cho phiên bản #lazy. Nhược điểm có lẽ là người dùng trừu tượng của chúng tôi thực sự phải hiểu khi nào nên gọi #lazy (ví dụ: trước khi gọi #drop (n)). Phía trên là mã sạch. –

+0

Tôi đã gặp rất nhiều rắc rối với (và học được rất nhiều từ) #drop (n). Bây giờ tôi đang trở lại "đồng bằng" Enumerators ở khắp mọi nơi tôi đã phải rắc một vài ... lazy.drop (n) ... về. Vì vậy, tôi đã định nghĩa một phương thức giống như drop-up đơn giản là tiến lên Enumerator, cho phép tôi thay đổi chúng thành ... skip (n) ... –

+0

Right. Bạn chắc chắn có thể định nghĩa một phương thức như 'Enumerable # skip (n)' sẽ trả về một 'Enumerator' thay vì một mảng như' drop' làm và chơi với nó. –

Các vấn đề liên quan