[ANN] lazy.rb

I've got an 0.0 release of lazy.rb, an implementation of lazy evaluation
for Ruby, up here:

http://moonbase.rydia.net/software/lazy/

It provides two functions: promise (like R5 Scheme's delay) and force
(just as in Scheme).

promise takes a block to evaluate later; as an aid to circular
programming, the block's result is passed to it as a parameter.

force is used to force the value of a promised computation.

Some attempts have been made to support implicit forcing through the use
of SimpleDelegator, but with only mixed success so far.

-mental

MenTaLguY schrieb:

I've got an 0.0 release of lazy.rb, an implementation of lazy evaluation
for Ruby, up here:

http://moonbase.rydia.net/software/lazy/

It provides two functions: promise (like R5 Scheme's delay) and force
(just as in Scheme).

promise takes a block to evaluate later; as an aid to circular
programming, the block's result is passed to it as a parameter.

force is used to force the value of a promised computation.

Some attempts have been made to support implicit forcing through the use
of SimpleDelegator, but with only mixed success so far.

Hi mental,

do you have some unit tests or an example where implicit forcing isn't working as you'd like?

Regards,
Pit

Err... unit tests. Those would be nice.

But, here are a couple examples:

blah = promise { 3 }
p blah.methods.include? "+" # => false, failed to force
force( blah )
p blah.methods.include? "+" # => true

...and:

foo = promise { 3 }
begin
   p [][foo] # TypeError: can't convert Lazy::Thunk into Integer
rescue TypeError => e
   puts e
end
force( foo )
p [][foo] # => nil

Same problem in both cases, basically.

Oh, and if you try these in irb, remember that you will get different
results because irb's attempt to display the result value will force the
promise.

-mental

···

On Fri, 2005-11-04 at 16:42 +0900, Pit Capitain wrote:

Hi mental,

do you have some unit tests or an example where implicit forcing isn't
working as you'd like?

MenTaLguY schrieb:

...
Same problem in both cases, basically.

Here's another implementation that doesn't have these problems:

   module Lazy #:nodoc: all
     class Thunk
       instance_methods.each { |m| undef_method m unless m =~ /^__/ }
       def initialize( &computation )
         @computation = computation
       end
       def method_missing( *args, &block )
         ( @result ||= @computation.call( self ) ).send( *args, &block )
       end
     end
   end

Regards,
Pit

Pit Capitain wrote:

MenTaLguY schrieb:
> ...
> Same problem in both cases, basically.

Here's another implementation that doesn't have these problems:

   module Lazy #:nodoc: all
     class Thunk
       instance_methods.each { |m| undef_method m unless m =~ /^__/ }
       def initialize( &computation )
         @computation = computation
       end
       def method_missing( *args, &block )
         ( @result ||= @computation.call( self ) ).send( *args, &block )
       end
     end
   end

Ah, that clarifies things. This is essentially a pass-thru Functor with
a cached result. (Why is 'self' being passed to the computation
though?) While not quite the same as the implict lambda's I suggested,
it is interesting just how similar they are.

This whole Functor paradigm, including things like #every, #enum_for,
#as (was #superfunc), etc. and now this --all are various simple
decorators of a special form where the heart of code is some sort of
transfomation in method_missing. Insteresting stuff. It would be even
more interesting to see if this could somehow be embodied in core so to
operate more efficiently (and perhaps a syntax variation on the
methods?) --this techinque may have potential for roles and namespace
selectors too.

T.

Hmm, I can adapt this, I think. Thanks. It does have a few problems as
written; e.g.:

1. since @computation isn't set to nil after being run, the closure
    can't get garbage collected until the thunk is

2. the computation will get run multiple times if its result is nil
    or false

3. There's no way to unwrap the value from the thunk

But I think you got me over the hump. Look for another lazy.rb release
soonish.

-mental

···

On Sat, 2005-11-05 at 11:46 +0900, Pit Capitain wrote:

MenTaLguY schrieb:
> ...
> Same problem in both cases, basically.

Here's another implementation that doesn't have these problems:

   module Lazy #:nodoc: all
     class Thunk
       instance_methods.each { |m| undef_method m unless m =~ /^__/ }
       def initialize( &computation )
         @computation = computation
       end
       def method_missing( *args, &block )
         ( @result ||= @computation.call( self ) ).send( *args, &block )
       end
     end
   end

For convenience in circular programming; it's an easier way for the
computation to get access to its own future result.

Rather than having to do:

result = nil
result = promise {
   ... do something with result ...
}

It allows simply:

promise { |result|
   ... do something with result ...
}

-mental

···

On Sat, 2005-11-05 at 14:37 +0900, Trans wrote:

(Why is 'self' being passed to the computation though?)

MenTaLguY wrote:

2. the computation will get run multiple times if its result is nil
   or false

Use defined?

3. There's no way to unwrap the value from the thunk

What's in a value? That which we call a String by any other class would #to_s as neat.

Devin