I’ve started studying Ruby, and while I like it, one thing that bothers
me is that there is not a way to explicitly declare a variable in order
to say “I want a variable local to this scope: I don’t want to reuse
some variable by the same name in a containing scope.” In particular,
the fact that giving a block parameter the same name as an existing
variable can overwrite that variable troubles me.
Now I haven’t written enough Ruby code to know whether this is really a
problem in practice or if it is just a theoretical concern.
Nevertheless, I thought that the problem could be addressed if there
was some way to “declare” your local variables before you use them. I
put “declare” in quotes because that isn’t the right word in Ruby.
What I wanted was a facility to assert that a variable is not yet in
use.
I came up with the code that follows. The introductory comment
explains. I imagine that someone has already done this, but I’d be
interested to hear what folks think.
Thanks,
David Flanagan
module Kernel
Assert that the named variables do not exist yet,
so that they can be used as local variables in the block without
clobbering an existing variable
This method expects any number of variable names as arguments.
The names may be specified as symbols or strings.
The method must be invoked with an associated block, although the
block may be empty. It uses the binding of the block with eval to
check
whether the variable names are in use yet, and throws a NameError
if
any of them are currently used.
If the block associated with local expects no arguments, then this
method
invokes it. The code within the block can safely use the symbols
passed to local. If the block expects arguments, then local
assumes
that the block is intended for the caller and just returns it.
Here are typical some uses of this method:
local :x, :y do # Execute a block in which x and y are local
vars
data.each do |x|
y = x*x
puts y
end
end
Here’s a way to use local where nested blocks are not needed:
data.each &local(:x) {|x| puts x*x }
Here’s a way to use it as an assertion with an empty block
local(:x, :y) {} # Assert that x and y aren’t in use yet.
data.each do |x| # Now go use those variables
y = x*x
puts y
end
def local(*syms, &block)
syms.each do |sym|
# First, see if the symbol itself is defined as a variable or
method
# XXX: do I also need to check for methods like x=?
# XXX Would it be simpler or faster to do eval local_variables
instead?
value = eval(“defined? #{sym.to_s}”, block)
# If it is not defined, then go on to the next symbol
next if !value
# Otherwise, the symbol is in use, so raise an exception
raise NameError.new("#{sym} is already a #{value}")
end
# If none of the symbols are in use, then we can proceed.
# What we do next depends on the arity of the block, however.
# If the block expects no arguments, then we just call it
# If the block was declared with arguments, then it is not intended
# for this method. Instead, we return it so our caller can invoke
it.
if block.arity == 0 or block.arity == -1
block.call
else
block
end
end
end