| Path: | README.txt |
| Last Update: | Tue Jul 24 10:24:54 -0500 2007 |
Class Fluid provides dynamically scoped ("fluid") variables modeled after those of Common Lisp. It also gives you a convenient way to reversibly change the values of globals.
Here‘s an incredibly simple example:
require 'fluid'
Fluid.let(:var, 1) {
puts Fluid.var # prints 1
}
puts Fluid.var # error - Fluid.var has disappeared at this point.
Fluid.var is a "pseudovariable" that only exists within the block. What‘s interesting about fluid variables is what "within the block" means. Here‘s another example:
require 'fluid'
def putter
puts Fluid.var
end
Fluid.let(:var, 1) {
putter
}
putter
The first call of putter will successfully print 1. The second will fail because Fluid.var doesn‘t exist any more.
Fluid variables are useful in relatively few situations. They‘re good when you have some number of interconnected objects that need to share some value. You could pass the value around, but then intermediate methods that didn‘t care about it would have to pass it along to methods that did. You could use a global, but then the value would be visible to objects that ought not to be able to see it. Fluid variables let you "scope" the value to all methods called (directly or indirectly) from an entry point to the collection of connected objects.
This is especially handy when you want to change the value of the variable according to the depth of processing, as when you want to increase the indentation level of some sort of tracing output. Like this:
fact(5)
fact(4)
fact(3)
fact(2)
fact(1)
fact(1) => 1
fact(2) => 2
fact(3) => 6
fact(4) => 24
fact(5) => 120
One implementation of that would look like the following implementation of fact. It uses two utility methods that reduce clutter.
def fact(n)
trace "fact(#{n})" # trace() produces output with appropriate indentation.
result =
if n <= 1
n
else
deeper { n * fact(n-1) } # deeper() executes block with increased indentation.
end
trace "fact(#{n}) => #{result}"
result
end
Here‘s the support code.
Fluid.defvar(:indent_prefix, '') # Initial value of indentation.
def trace(text)
puts Fluid.indent_prefix + text # Use the value even though it wasn't passed in.
end
def deeper # "Rebind" the value of the variable for the scope of a block.
Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
yield
end
end
For an even less intrusive example, see the examples directory.
Here‘s a final example that shows the syntax and behavior of Fluid.let. See also the more formal description at Fluid.let.
Fluid.let([:var1, 1],
[:var2, 2]) {
puts(Fluid.var1) # prints 1
puts(Fluid.var2) # prints 2
Fluid.let([:var2, "new 2"],
[:var3, "new 3"]) {
puts(Fluid.var1) # prints 1, as above.
puts(Fluid.var2) # prints "new 2"
puts(Fluid.var3) # prints "new 3"
}
puts(Fluid.var1) # prints 1
puts(Fluid.var2) # Back to printing 2
puts(Fluid.var3) # error - that variable no longer exists.
}
If you wish, you can also use Fluid.let to temporarily change the value of globals. For example, the global #, is used as an implicit argument to Array#join. You could do the following:
Fluid.let("$,", "-") do
puts [1, 2, 3].join #=> "1-2-3"
end
puts [1, 2, 3].join #=> "123"
The advantage of this over just setting $, and then resetting it is that Fluid takes care of dealing with exceptions.
You‘re not allowed to rebind all globals. The I/O globals ($stdout, etc.) have special-case behavior that‘s either impossible or not safe to cope with.