Class: Fluid

Inherits:
Object
  • Object
show all
Defined in:
lib/fluid.rb

Overview

Fluid (dynamically scoped) variables for Ruby. See README.txt.

Defined Under Namespace

Classes: Environment, FluidVar, GlobalVar, Var

Constant Summary collapse

Version =
"1.0.1"
@@environment =

All fluid variables are managed by one environment.

Environment.new

Class Method Summary collapse

Class Method Details

.defvar(name, value = nil) ⇒ Object

A global declaration of a fluid variable. After executing this code:

  Fluid.defvar(:global, 5) 

Fluid.global will normally everywhere have the value 5, unless it's
changed by assignment or Fluid.let.

However, Fluid.defvar has effect only the first time it's executed.
That is, given this sequence:

  Fluid.defvar(:global, 5)
  Fluid.defvar(:global, 6666666)

Fluid.global has value 5. The second Fluid.defvar is ignored. 
The "a-bit-more-clever-fluid-tracing.rb" example shows how 
this can be useful.

A Fluid.defvar executed while a Fluid.let block is in effect will
have no effect:

  Fluid.let(:not_global, 1) {
    Fluid.defvar(:not_global, 5555)   # Fluid.not_global == 1
  }
  # The defvar has had no effect, so Fluid.not_global
  # has no value after the block.

Fluid.defvar can take a block as an argument:

  Fluid.defvar(:var) { long_and_expensive_computation }

The block is only executed if its value would be assigned to the
variable.

The first argument to Fluid.defvar may not be the name of 
a global variable.


295
296
297
298
299
300
301
302
303
304
305
# File 'lib/fluid.rb', line 295

def Fluid.defvar(name, value = nil)
  if Var.global?(name)
    raise NameError, "Fluid.defvar of a global can never have an effect, so it's not allowed."
  end

  var = Var.build(name, @@environment)
  unless @@environment.has?(var.name)
    value = yield if block_given?
    var.push_binding(value)
  end
end

.has?(name) ⇒ Boolean

Answers whether name is already a fluid variable.

Returns:

  • (Boolean)


308
309
310
311
# File 'lib/fluid.rb', line 308

def Fluid.has?(name)
  return true if Var.global?(name)
  @@environment.has?(Var.ensure_symbol(name))
end

.let(*var_specs) ⇒ Object

Puts the variable specifications in effect, then executes the block, undoes the variable specifications, and returns the block’s value.

The simplest form of variable specification is a variable name (symbol or string). In this case, the variable starts out with the value nil:

Fluid.let(:will_have_value_nil) {...}

You can also specify the initial value of the variable:

Fluid.let(:will_have_value_1, 1) {...}

Multiple variables can be specified in a single let. Each one must be in an array, even if given no value:

Fluid.let([:will_have_value_1, 1],
          [:starts_as_nil]) {...}

From the moment the block begins to execute until the moment it returns, getters and setters for the variables are made class methods of Fluid:

Fluid.let(:var, 1) {
  Fluid.var       # has value 1
  Fluid.var = 2   # change value to 2
}

References to the variable needn’t be in the lexical scope of the Fluid.let. They can be anywhere in the program. (To be more precise: references can be made whenever the original Fluid.let is on the stack.)

When Fluid.let’s block exits, the value of the variable is no longer accessible through Fluid.var. An attempt to access it will raise a NameError. If, however, there’s another Fluid.let binding the same name still on the stack, that version’s value is back in effect. For example:

Fluid.let(:var, 1) {            # Fluid.var => 1
   Fluid.var = 2                # Fluid.var => 2
   Fluid.let(:var, "hello") {   # Fluid.var => "hello"
      Fluid.var *= 2            # Fluid.var => "hellohello"
   }                            # Fluid.var => 2
}                               # Fluid.var => raises NameError

Variable values are undone even if the block exits with a throw or an exception.

If a variable name begins with ‘$’, Fluid.let realizes it’s a global variable and gives that variable a new value. No getters or setters are created. The old value is still restored when the block exits.

Destructors

There may be an argument past the initial value, the value destructor. It may be either a Proc or the name of a method. If it’s the name of a method, it’s sent as a message to the value of the second argument. Like this:

Fluid.let(out, File.open("logfile", "w"), :close)

(You wouldn’t really do that, though, since File.open does the same thing for you.)

If the third argument is a block, it’s called with the value of the second argument as its single parameter.

Fluid.let(:out, File.open("logfile", 'w'),
          proc {|io| io.close}) {...}


249
250
251
252
253
254
255
256
257
# File 'lib/fluid.rb', line 249

def Fluid.let(*var_specs)
  unwind_list = create_dynamic_context(var_specs)

  begin
    return yield if block_given?
  ensure
    unwind_dynamic_context unwind_list
  end
end

.method_missing(symbol, *args) ⇒ Object

:nodoc:

Raises:

  • (NameError)


313
314
315
316
# File 'lib/fluid.rb', line 313

def Fluid.method_missing(symbol, *args) # :nodoc:
  symbol = symbol.to_s.gsub(/=/, '')
  raise NameError, "'#{symbol}' has not been defined with Fluid.let or Fluid.defvar."
end