Now that I’m done asking stupid questions about iterators, it’s time for
another release of lazy.rb!
== WHAT IS THIS THING? ==
lazy.rb is a library which provides lazy evaluation for Ruby. It allows
you to optimistically defer computations until they are required.
These deferred computations are represented by “promises”:
prom = promise { 2 * 3 }
p prom #=> #<Lazy::Promise computation=#Proc:[email protected]:1>
result = demand prom
p result #=> 6
A promise is only evaluated once, after which it becomes essentially
indistinguishable from its result object:
p prom #=> 6
p prom.class #=> Fixnum
In fact, you can simply use the promise directly and it will be
evaluated for you automatically:
prom = promise { 8 + 1 }
num = prom * 2
p num #=> 18
p prom #=> 9
There’s one caveat; Ruby always considers promises, evaluated or not, to
be true. If you are testing a result for truth, you must unwrap the
result with demand.
prom = promise { false }
demand prom # force evaluation
p prom #=> false
puts “wait, it’s true?” if prom #=> wait, it’s true?
puts “no, false. much better.”
unless demand( prom )
#=> no, false. much better.
Conveniently, calling demand on an already evaluated promise (or even on
a regular object) is harmless.
p demand( 6 ) #=> 6
== WHAT’S NEW THIS TIME AROUND? ==
Kernel#force has been renamed to Kernel#demand, though Kernel#force
still exists as an alias. I’m still wavering on the final name.
Feedback is welcomed.
I’ve also incorporated a very nice suggestion from Tom P., so that
Object#inspect no longer forces the evaluation of a promise. This makes
promises much easier to work with in irb.
The biggest new feature is an API for lazy streams. Lazy streams are
linked lists of deferred computations. Iterating over the list forces
its evaluation, cell by cell. They can be used for many purposes,
including a substitute for generators or threads.
As linked lists, each cell in the stream (Lazy::Cons) has two
attributes: first, and rest. first is the value in that cell, and rest
is a reference to the tail (remainder) of the list.
There is also a Lazy::Stream class, which wraps a lazy stream and
implements Enumerable.
The API tries to make both imperative and “pure” stream creation easy.
For example:
create a stream of lines from stdin
stdin_lines = Lazy::generate_stream do |tail|
line = $stdin.gets
if line
[ line, tail ]
else
nil
end
end
create a stream of Fibbonacci numbers
def make_fibs( a=1, b=0 )
Lazy::cons_stream do
sum = a + b
[ sum, make_fibs( b, sum ) ]
end
end
fibs = make_fibs
You can use them directly, as linked lists:
echo lines from stdin
iter = stdin_lines
while demand( iter )
puts iter.first
iter = iter.rest
end
…or via the Enumerable interface:
print the Fibbonacci numbers up through 100
enum = Lazy::Stream.new( fibs )
enum.each do |n|
puts n
break if n > 100
end
The lazy streams API is still experimental and missing some important
functionality (take and drop, for example), but it’s already useful at
this point and I’d like to get some feedback.
== WHERE CAN I GET IT? ==
I’ve got a small web site here:
http://moonbase.rydia.net/software/lazy.rb/
lazy.rb is available for download as both a tarball and a gem. This is
my first real gem, so let me know if you have any problems with it.
There’s also a link to the API documentation, but sadly nothing in the
way of a tutorial yet.
I’ll eventually also start publishing my git repository. I keep meaning
to get around to it…
-mental