What is the advantage of creating an enumerable object using to_enum in Ruby?

半腔热情 提交于 2019-12-12 09:32:34

问题


Why would you create a proxy reference to an object in Ruby, by using the to_enum method rather than just using the object directly? I cannot think of any practical use for this, trying to understand this concept & where someone might use it, but all the examples I have seen seem very trivial.

For example, why use:

"hello".enum_for(:each_char).map {|c| c.succ }

instead of

"hello".each_char.map {|c| c.succ }

I know this is a very simple example, does anyone have any real-world examples?


回答1:


Most built-in methods that accept a block will return an enumerator in case no block is provided (like String#each_char in your example). For these, there is no reason to use to_enum; both will have the same effect.

A few methods do not return an Enumerator, though. In those case you might need to use to_enum.

# How many elements are equal to their position in the array?
[4, 1, 2, 0].to_enum(:count).each_with_index{|elem, index| elem == index} #=> 2

As another example, Array#product, #uniq and #uniq! didn't use to accept a block. In 1.9.2, this was changed, but to maintain compatibility, the forms without a block can't return an Enumerator. One can still "manually" use to_enum to get an enumerator:

require 'backports/1.9.2/array/product' # or use Ruby 1.9.2+
# to avoid generating a huge intermediary array:
e = many_moves.to_enum(:product, many_responses)
e.any? do |move, response|
  # some criteria
end 

The main use of to_enum is when you are implementing your own iterative method. You typically will have as a first line:

def my_each
  return to_enum :my_each unless block_given?
  # ...
end



回答2:


I think it has something to do with internal and external Iterators. When you return an enumerator like this:

p = "hello".enum_for(:each_char)

p is an external enumerator. One advantage of external iterators is that:

External iterators are more flexible than internal iterators. It's easy to compare two collections for equality with an external iterator, for example, but it's practically impossible with internal iterators…. But on the other hand, internal iterators are easier to use, because they define the iteration logic for you. [From The Ruby Programming Language book, ch. 5.3]

So, with external iterator you can do, e.g.:

p = "hello".enum_for(:each_char)
loop do
    puts p.next
end



回答3:


Let's say we want to take an array of keys and an array of values and sew them up in a Hash:

With #to_enum

def hashify(k, v)
  keys = k.to_enum(:each)
  values = v.to_enum(:each)
  hash = []
  loop do
    hash[keys.next] = values.next
    # No need to check for bounds,
    # as #next will raise a StopIteration which breaks from the loop
  end
  hash
end

Without #to_enum:

def hashify(k, v)
  hash = []
  keys.each_with_index do |key, index|
    break if index == values.length
    hash[key] = values[index]
  end
  hash
end

It's much easier to read the first method, don't you think? Not a ton easier, but imagine if we were somehow manipulating items from 3 arrays? 5? 10?




回答4:


This isn't quite an answer to your question, but hopefully it is relevant.

In your second example you are calling each_char without passing a block. When called without a block each_char returns an Enumerator so your examples are actually just two ways of doing the same thing. (i.e. both result in the creation of an enumerable object.)

irb(main):016:0> e1 = "hello".enum_for(:each_char)
=> #<Enumerator:0xe15ab8>
irb(main):017:0> e2 = "hello".each_char
=> #<Enumerator:0xe0bd38>
irb(main):018:0> e1.map { |c| c.succ }
=> ["i", "f", "m", "m", "p"]
irb(main):019:0> e2.map { |c| c.succ }
=> ["i", "f", "m", "m", "p"]



回答5:


It's great for large or infinite generator objects. E.g., the following will give you an enumerator for the whole Fibonacci seequence, from 0 to infinity.

def fib_sequence
  return to_enum(:fib_sequence) unless block_given?
  yield 0
  yield 1
  x,y, = 0, 1
  loop { x,y = y,x+y; yield(y) }
end

to_enum effectively allows you to write this with regular yields without having to mess with Fibers.

You can then slice it as you want, and it will be very memory efficient, since no arrays will be stored in memory:

module Slice
    def slice(range)
        return to_enum(:slice, range) unless block_given?
        start, finish = range.first, range.max + 1
        copy = self.dup
        start.times { copy.next }
        (finish-start).times { yield copy.next }
    end
end
class Enumerator
    include Slice
end

fib_sequence.slice(0..10).to_a
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
fib_sequence.slice(10..20).to_a                                                                                                                           
#=> [55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]


来源:https://stackoverflow.com/questions/2863044/what-is-the-advantage-of-creating-an-enumerable-object-using-to-enum-in-ruby

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!