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.

== An Alternative to 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.
}


== Globals

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.