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