From: dflanagan@... Date: 2007-01-19T15:15:09+09:00 Subject: local variable assertion 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