D
Daniel Nugent
Hello fellow Rubyists,
Something got to niggling me in the back of my mind last week during a
lecture in my Programming Languages class about laziness in functions.
Basically, the point is that functions that don't evaluate until they
need to are cool.
We can be pretty lazy right now with code generators that will let us
fake infinite series and stuff like that, but I think we can be even
LAZIER if we put our minds to it.
Along these lines, I came up with this:
class Lazy
=20
#DESTROY ALL METHODS! DESTROY ALL INSTANCE METHODS!
instance_methods.each { |m| undef_method m unless m =3D~ /^__/ }
def initialize(switch =3D ne_eval, &block)
@code_block =3D block
@mode =3D switch
end
=20
def method_missing(symbol, *args)
if @mode =3D=3D ne_eval
@real_obj ||=3D @code_block.call
@real_obj.__send__(symbol, *args)
else
@code_block.call().__send__(symbol, *args)
end
end
end
What this does is let us wait until a value is really, actually needed
before the code that would provide it is evaluated. This is nice if
you wanted to put off that expensive retrieval operation until it was
actually needed, if at all, and if you're tricky about it, you can get
non-deterministic results just like the bastiche you want to be (just
set the switch to something besides ne_eval, now the block is called
ALL the time!).
With a little bit of tricksyness, we can use this to let us turn
regular variables into dataflow variables, which, instead of causing
all sorts of errors because they're not ready yet, merely make the
program wait until they're darned well not nil (or whatever condition=20
you happen to need). For example:
#Dataflow example
foo =3D Lazy.new do
while $data_flow_variable.nil?
end
$data_flow_variable
end
bar =3D Thread.new {print foo}
baz =3D Thread.new {sleep(3);$dfv =3D "Hello World, sorry I'm late"}
bar.join
baz.join
This is, of course, trivial, but it demonstrates how you might employ
data flow for fun and profit (Especially if you could abstract
laziness and dataflowism into functions that did all the boring object
creation work for you, which, of course, you could).
The question, of course, is where are the gaps here? Where does this
idea just fall apart and spew, SPEW all over the floor? I'm
reasonably sure you're all much more masterful at Ruby than I am and
stand a better chance at figuring out what could explode (I personally
expect that assignment will botch it up, but I can't be sure.) And,
if explosion is imminent, can it be fixed?
Thanks for reading!
PS: Thanks to Aria and binary42 for smacking me around a little and
pointing me to {|one, step, back|} and the BlankSlate idea there.
--=20
-Dan Nugent
Something got to niggling me in the back of my mind last week during a
lecture in my Programming Languages class about laziness in functions.
Basically, the point is that functions that don't evaluate until they
need to are cool.
We can be pretty lazy right now with code generators that will let us
fake infinite series and stuff like that, but I think we can be even
LAZIER if we put our minds to it.
Along these lines, I came up with this:
class Lazy
=20
#DESTROY ALL METHODS! DESTROY ALL INSTANCE METHODS!
instance_methods.each { |m| undef_method m unless m =3D~ /^__/ }
def initialize(switch =3D ne_eval, &block)
@code_block =3D block
@mode =3D switch
end
=20
def method_missing(symbol, *args)
if @mode =3D=3D ne_eval
@real_obj ||=3D @code_block.call
@real_obj.__send__(symbol, *args)
else
@code_block.call().__send__(symbol, *args)
end
end
end
What this does is let us wait until a value is really, actually needed
before the code that would provide it is evaluated. This is nice if
you wanted to put off that expensive retrieval operation until it was
actually needed, if at all, and if you're tricky about it, you can get
non-deterministic results just like the bastiche you want to be (just
set the switch to something besides ne_eval, now the block is called
ALL the time!).
With a little bit of tricksyness, we can use this to let us turn
regular variables into dataflow variables, which, instead of causing
all sorts of errors because they're not ready yet, merely make the
program wait until they're darned well not nil (or whatever condition=20
you happen to need). For example:
#Dataflow example
foo =3D Lazy.new do
while $data_flow_variable.nil?
end
$data_flow_variable
end
bar =3D Thread.new {print foo}
baz =3D Thread.new {sleep(3);$dfv =3D "Hello World, sorry I'm late"}
bar.join
baz.join
This is, of course, trivial, but it demonstrates how you might employ
data flow for fun and profit (Especially if you could abstract
laziness and dataflowism into functions that did all the boring object
creation work for you, which, of course, you could).
The question, of course, is where are the gaps here? Where does this
idea just fall apart and spew, SPEW all over the floor? I'm
reasonably sure you're all much more masterful at Ruby than I am and
stand a better chance at figuring out what could explode (I personally
expect that assignment will botch it up, but I can't be sure.) And,
if explosion is imminent, can it be fixed?
Thanks for reading!
PS: Thanks to Aria and binary42 for smacking me around a little and
pointing me to {|one, step, back|} and the BlankSlate idea there.
--=20
-Dan Nugent