From: David Masover Date: 2009-11-22T07:19:34+09:00 Subject: Re: Newbie question: Enumerators On Saturday 21 November 2009 03:35:03 pm Omran Nazir wrote: > Hi, I know this is a very newbie question but its one concept that despite > google and the O'Riley book I cant get my head around. > > Can someone please give a simple explantion of what exactly an Enumerator > is, Something which provides several things you usually find in an Enumerable object, such as #each. Probably the simplest example of why you would want one is to take something that behaves like #each and use other fun tools like #map. A useless example -- you already know what map does: (1..10).map{|x| x*2} Now you can combine it with other iterators: (1..10).each_slice(2).map{|a,b| a+b} If you want to see what each_slice is doing, try this: (1..10).each_slice(2).to_a Basically, each_slice is returning an enumerator which, instead of just giving us the numbers 1-10, it gives us the pairs of numbers -- 1,2; then 3,4; and so on. You can do other fun (but pointless) hacks like: 10.times.map{|x|x+1} == (1..10).to_a Another reason you would want one is the ability to call enum.next repeatedly -- to invert control, in a way. That is, rather than doing this: File.open('foo') do |file| file.each_line do |line| # do something with line end end You could instead do this: File.open('foo') do |file| enum = file.each_line begin loop do line = enum.next # do something with line end rescue StopIteration # end of file end end Why would you want to? Well, in case you wanted to do those reads out of order -- basically, it lets you control when and how you read a line. Here's an example that's closer to being practical: File.open('foo') do |file| enum = file.each_line begin loop do line = enum.next.chomp while line =~ /\\$/ line = line.chop + enum.next.chomp end # do something with line... end rescue StopIteration # end of file end end Basically, this looks for any line that ends in a backslash and treats that as a continued line. But for that to work, we're essentially having to read ahead -- notice that second "enum.next" in there. File#each_line won't work, but a technique like this might be useful to implement, say, File#each_continued_line. > what it can act on Anything that has an #each method, or any method that behaves similarly. Look up the documentation for Object#enum_for for an example. In fact, these days, whenever I write any sort of iterator, I make sure to return an enum if I don't get a block. That's how the above each_line works. > and why you can pass 'array.to_enum' to a method > that expect 'array'? Most methods don't "expect" any particular type at all, probably only a behavior. I don't know what method you're talking about, but they probably just expect something that implements 'each'. Google "duck typing" for more on this philosophy. By the way, I may have told you _way_ more than you ever wanted to know, as there's absolutely no reason for passing 'array.to_enum' to a method that would've been happy with 'array', because 'array.to_enum.each' does exactly the same thing as 'array.each', only slightly less efficiently. > What sort of objects can be defined as 'enumerable' > and what makes them so? Well, again, see duck typing. There's nothing special about making an object "enumerable". The easiest way to do so is to include the Enumerable module, but no sane code would ever check that -- if needed, you could rewrite everything Enumerable does. For a semi-formal definition, look up the documentation for that Enumerable module. Anything that includes that module can probably be considered Enumerable. Anything that behaves like that module is by definition Enumerable. Note that the Enumerator class uses Enumerable. > What is the difference between and Enumerator and > an Iterator? I'm not sure what an iterator is. I'm guessing it's meant to be a method like each. Call it with a block, and it runs the block on each element of itself. Call it without a block, and it returns an Enumerator for itself. Unless the word "iterator" is being used a lot, I wouldn't worry too much. I only see it once in the definition -- iterator? is an alias for block_given?