When is the Enumerator::Yielder#yield method useful?

前端 未结 4 947
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-13 12:03

The question \"Meaning of the word yield\" mentions the Enumerator::Yielder#yield method. I haven\'t used it before, and wonder under what circumstances it would be

相关标签:
4条回答
  • 2021-02-13 12:09

    "How to create an infinite enumerable of Times?" talks about constructing and lazy iterators, but my favorite usage is wrapping an existing Enumerable with additional functionality (any enumerable, without needing to know what it really is, whether it's infinite or not etc).

    A trivial example would be implementing the each_with_index method (or, more generally, with_index method):

    module Enumerable
      def my_with_index
        Enumerator.new do |yielder|
          i = 0
          self.each do |e|
            yielder.yield e, i
            i += 1
          end
        end
      end
    
      def my_each_with_index
        self.my_with_index.each do |e, i|
          yield e, i
        end
      end
    end
    
    [:foo, :bar, :baz].my_each_with_index do |e,i|
      puts "#{i}: #{e}"
    end
    #=>0: foo
    #=>1: bar
    #=>2: baz
    

    Extending to something not already implemented in the core library, such as cyclically assigning value from a given array to each enumerable element (say, for coloring table rows):

    module Enumerable
      def with_cycle values
        Enumerator.new do |yielder|
          self.each do |e|
            v = values.shift
            yielder.yield e, v
            values.push v
          end
        end
      end
    end
    
    p (1..10).with_cycle([:red, :green, :blue]).to_a # works with any Enumerable, such as Range
    #=>[[1, :red], [2, :green], [3, :blue], [4, :red], [5, :green], [6, :blue], [7, :red], [8, :green], [9, :blue], [10, :red]]
    

    The whole point is that these methods return an Enumerator, which you then combine with the usual Enumerable methods, such as select, map, inject etc.

    0 讨论(0)
  • 2021-02-13 12:13

    For example you can use it to construct Rack response bodies inline, without creating classes. An Enumerator can also work "outside-in" - you call Enumerator#each which calls next on the enumerator and returns every value in sequence. For example, you can make a Rack response body returning a sequence of numbers:

    run ->(env) {
      body = Enumerator.new do |y|
       9.times { |i| y.yield(i.to_s) }
      end
      [200, {'Content-Length' => '9'}, body]
    }
    
    0 讨论(0)
  • 2021-02-13 12:17

    It seems to be useful when you have multiple objects you want to enumerate over, but flat_map isn't suitable, and you want to chain the enumeration with another action:

    module Enumerable
      def count_by
        items_grouped_by_criteria = group_by {|object| yield object}
        counts = items_grouped_by_criteria.map{|key, array| [key, array.length]}
        Hash[counts]
      end
    end
    
    def calculate_letter_frequencies
      each_letter.count_by {|letter| letter}
    end
    
    def each_letter
      filenames = ["doc/Quickstart", "doc/Coding style"]
      # Joining the text of each file into a single string would be memory-intensive
      enumerator = Enumerator.new do |yielder|
        filenames.each do |filename|
          text = File.read(filename)
          text.chars.each {|letter| yielder.yield(letter)}
        end
      end
      enumerator
    end
    
    calculate_letter_frequencies
    
    0 讨论(0)
  • 2021-02-13 12:25

    Since Mladen mentioned getting other answers, I thought I would give an example of something I just did earlier today while writing an application that will receive data from multiple physical devices, analyze the data, and connect related data (that we see from multiple devices). This is a long-running application, and if I never threw away data (say, at least a day old with no updates), then it would grow infinitely large.

    In the past, I would have done something like this:

    delete_old_stuff if rand(300) == 0
    

    and accomplish this using random numbers. However, this is not purely deterministic. I know that it will run approximately once every 300 evaluations (i.e. seconds), but it won't be exactly once every 300 times.

    What I wrote up earlier looks like this:

    counter = Enumerator.new do |y|
      a = (0..300)
      loop do
        a.each do |b|
          y.yield b
        end
        delete_old_stuff
      end
    end
    

    and I can replace delete_old_stuff if rand(300) == 0 with counter.next

    Now, I'm sure there is a more efficient or pre-made way of doing this, but being sparked to play with Enumerator::Yielder#yield by your question and the linked question, this is what I came up with.

    0 讨论(0)
提交回复
热议问题