I'm just starting with Ruby, but one of the tricky parts of this language is that since it has no way to declare a variable, you can't tell it that you want a variable to be local. When you assign a value to a variable, you will be using a variable from a containing scope, if any such variable exists. And if no such variable exists, then you'll be creating a new one in the local scope. Block parameters also work this way, which is confusing.
I've attempted to create a workaround. My local() method asserts that the named variables do not yet exist, guaranteeing that any uses that follow will create local variables instead of clobbering existing variables. My code is in the extended entry. See the introductory comment for usage information.
I don't have enough Ruby experience yet to know if this is actually something useful, or just a curiosity. Creating this function was an interesting exercise for me, so I haven't looked yet to see if something like this already exists.
Read on for the code...
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




This looks a little like the new "let" command in JavaScript 1.7 but I haven't looked too closely that yet.
Could you not just use new variable names that haven't been used yet? This would avoid the clobbering. Ruby is not a safety language and the developer is free to shoot himself in the foot frequently.
One interesting part of the Ruby style of programming is that Ruby programmers aspire to write five line functions where having nested scopes is not usually an issue. If this sort of clobbering becomes a problem it may be that the code should be factored out to a new function.
Peter,
Yes, my local() function is like let. If I recall correctly, let is from Lisp, so it fits nicely in Ruby, too. Perhaps I should rename it.
You do still have to choose variables names that haven't been used yet. My function is merely an assertion that they are unused. If you specify the name of a variable that is already in use, it will raise an exception.
I agree with you that many Ruby programs will not need, and many Ruby programmers will not want, this kind of assertion. I'm not even sure whether I'd use it routinely. But it was certainly interesting to write my first function that behaves like a language keyword.
I should point out that in Ruby you can easily make your function take an optional block, without having to use:
local( :x ) {}
Change your code to:
def local(*syms, &block)
syms.each do |sym|
value = eval("defined? #{sym.to_s}", block)
next if !value
raise NameError.new("#{sym} is already a #{value}")
end
return unless block_given?
yield
end
gga,
Thanks. I need the block, however, so that the eval() is done using the correct bindings, so using block_given? doesn't help me.
I agree, however, that the arity thing is questionable...
gga,
I didn't read your code carefully enough or explain myself...
What you're missing in my rewrite is that if the block exists and expects arguments, I acually want to return it. That enables this style of invocation:
# data.each &local(:x) {|x| puts x*x }
local() returns its block so it can be "passed" to the data.each iterator.