Constants and scope in loops with Ruby
In this question I found an interesting detail about scope of a final
variable in Java. I don't know Java good enough, but I think that final
is identical to a constant in Ruby.
In C++ this is possible:
for(int i = 0; i < 5; ++i){
const int c = i * 5;
std::cout << c << std::endl;
}
Trying to change the value during the loop is not possible though and gives you a compile time error.
I was curious to see how Ruby would handle this, started irb and wrote this code to test it:
5.times do |x|
XPI = x * Math::PI
puts x
end
the result was
0.0
(irb):27: warning: already initialized constant XPI
3.141592653589793
(irb):27: warning: already initialized constant XPI
6.283185307179586
(irb):27: warning: already initialized constant XPI
9.42477796076938
(irb):27: warning: already initialized constant XPI
12.566370614359172
=> 5
So my question: Is there a way to assign a constant at the beginning of a loop that gets initialized for each loop iteration without creating warning messages? It could have some real world use cases, when I want to make a calculation based on the iterator variable and then make sure, the result开发者_运维百科 does not get changed for the remaining loop.
Nothing you need every single day, but I'm just curious.
In short, no. But there are couple of things that you should be aware of.
CONSTS
, as you already saw, aren't constant. They can be reassigned, with just a warning. Also, you can't assign to a constant within a method.
But even more, they don't impose anything about the object they hold: you can mutate the object with no problem, even without a warning:
STRING_CONST = 'foo'
#=>'foo'
STRING_CONST << 'bar'
#=>'foobar'
However, there is Object#freeze
method which causes an exception to rise when you try to alter a frozen object:
CONST_STRING = 'foo'
#=> "foo"
CONST_STRING.freeze
CONST_STRING << 'bar'
RuntimeError: can't modify frozen string
from (irb):12
from /usr/bin/irb:12:in `<main>'
So, in short, using CONSTANTS
discourages (but doesn't prevent) reassignment, and freezing objects prevent mutation. Neither of these could help you, as freezing numbers has no sense as they are already immutable.
There seems to be no strict equivalent to Java's final in ruby. However, you could use remove_const
(which is a private method in Module
) to get rid of the constant (and the warnings) at the end of the loop:
5.times do |x|
XPI = x * Math::PI;
puts x;
Object.instance_eval{ remove_const :XPI };
# this would work, too: Object.send(:remove_const, :XPI);
end
I don't think a constant is appropriate for this. After all, you use the name to refer to different values in succession. A true CONSTANT would be constant throughout the lifetime of the program. At least, that's the convention for using a fully capitalized name.
In some languages you could ensure, sometimes by default, that the value wouldn't change. Ruby is not such a language. Even true constants can be changed.
What you have is just a local variable that happens to be calculated once at the beginning of the loop. You should give it a regular name. If you want to be sure it doesn't change, you need to pay attention while coding, write tests to verify that the loop does what you expect it to and perhaps have someone review the code.
精彩评论