From: Robert Klemme Date: 2008-11-01T20:38:57+09:00 Subject: Re: Iterator objects and lazy evaluation On 01.11.2008 09:58, Yuh-Ruey Chen wrote: > Two questions: > > 1) Are there equivalents for iteration/enumeration functions like map > that return iterator/enumeration objects (in a Python sense)? > > An example: > > def read_files(files) > files.each {|file| > # blah > } > end > read_file(['file1', 'file2', 'file3'].map_iter {|x| open(x)}) > # map_iter would return an iterator object immediately instead of > opening every file and storing them into an array There is one issue with this design: the IO object is not closed properly. > I know that I could do this instead: > > ['file1', 'file2', 'file3'].each {|x| > open(x) { > # blah > } > } > > but sometimes it's inconvenient to do that if there's already a > function that accepts an Enumerable such as read_files above. So read_files expects an enumeration of opened IO objects, I guess. And you want to make sure that files are only opened on demand, correct? You could do something like this module Enumerable FileIter = Struct.new :enum do include Enumerable def each(&b) enum.each do |file_name| File.open(file_name, &b) end self end end def file_iter FileIter.new self end end Robert@babelfish2 ~ $ echo 1 > a Robert@babelfish2 ~ $ echo 2 > b irb(main):016:0> def read_files(files) irb(main):017:1> files.each {|io| p io.read} irb(main):018:1> end => nil irb(main):019:0> read_files %w{a b}.file_iter "1\n" "2\n" => # irb(main):020:0> Note that there is ARGF which basically does a similar thing: it opens and closes all files named in ARGV and presents them as a single IO object. Your approach feels a bit off the usual Ruby way and I am suspecting that you are trying to force foreign patterns into the language. I'd also say there is a design issue with your version of read_files: basically it iterates over a collection of file handles and does something to each one of them. Given the elegance and shortness of Ruby's iteration idiom the function read_files would rather be called read_file and process a single IO only. > 2) Is there some lazy evaluation library that can recalculate lazy > expression when values change? Something with the following semantics > (or something like it): > > x = lazy {a + b} > a = 1 > b = 2 > p x # 3 (calculates a + b) This cannot work IMHO since local variables a and b are defined *after* the block. > p x # 3 (memoized value) > a = 3 > p x # 5 (recalculates a + b) > a = [1,2] > b = [3,4] > p x # [1,2,3,4] (recalculates a + b) > p x # [1,2,3,4] (memoized value) > a[1] = '.' > p x # [1,'.',3,4] (recalculates a + b) > > I know there's a library called lazy.rb, but it's not exactly what I'm > looking for as it doesn't implement this functionality. IMHI you better explicitly provide arguments to whatever you use to lazily calculate values. One way is memoize and another option is to use a Hash for this if there is just one argument: irb(main):023:0> square = Hash.new {|h,k| puts "called"; h[k] = k * k} => {} irb(main):024:0> square[1000000] called => 1000000000000 irb(main):025:0> square[1000000] => 1000000000000 irb(main):026:0> square[1000000] => 1000000000000 irb(main):027:0> If you have more arguments, you can do def lazy(&b) cache = {} lambda {|*a| cache[a] ||= b[*a]} end irb(main):006:0> plus2 = lazy {|a,b| puts "called #{a} + #{b}"; a+b} => # irb(main):007:0> plus2[1,2] called 1 + 2 => 3 irb(main):008:0> plus2[1,2] => 3 irb(main):009:0> plus2[2,1] called 2 + 1 => 3 irb(main):010:0> plus2[2,1] => 3 irb(main):011:0> Kind regards robert