Ruby has no constant values? - ruby

It's possible to write this way
class Foo
MY_CONST = 100
end
and it's also possible to change it Foo::MY_CONST = 123
There will be a warning from a Ruby compiler, but anyway a constant will be changed.
So Ruby has no constant values?

it depends what kind of action you want to do with your constants.
If you have an
ARRAY = [1,2,3]
#and then
ARRAY << 4
Ruby won't complain.
However, if you
ARRAY = [1,2,3].freeze
#and
ARRAY << 4
#RuntimeError: can't modify frozen Array
You can still
ARRAY = [1,2,3,4]
#warning: already initialized constant ARRAY

If you freeze FOO, then trying to reassign FOO::MY_CONST will create a RuntimeError.
class FOO
MY_CONST = 100
end
FOO.freeze
FOO::MY_CONST = 123
gives
RuntimeError: can't modify frozen Class

They are semantically constants, so you can expect people not to change them. I'd call them liberal constants, see http://pastie.org/4608297

Related

What is the difference between a constant and a variable in Ruby?

So, I'm doing a Ruby course on CodeAcademy and I'm stuck in differentiating the difference between a variable and a class. Can someone please explain the difference to me? I'll give you cookies! ^^. No matter where I look online I can't find any information on this.
The idea of constants in Ruby is that they can get a value assigned only once while you can assign a new value to a variable as many times as you want. Now technically, you can assign a new value even to a constant. Ruby will however issue a warning in this case and you should try to avoid this case.
I guess the main point leading to confusion of people new to Ruby is that even values assigned to constants can be modified without a warning (e.g. by adding new elements to an array). References by a constant are no different to variables here in that the reference does not restrict what can be done with the value. The object referenced by either a variable or constant is always independent from that.
In this example, I assign a new array to the ARRAY constant. Later, I can happily change the array by adding a new member to it. The constant is not concerned by this.
ARRAY = []
# => []
ARRAY << :foo
ARRAY
# => [:foo]
The only thing forbidden (or, well, allowed with a warning) is if you try to assign a completely new value to a constant:
ARRAY2 = []
# => []
ARRAY2 = [:bar]
# warning: already initialized constant ARRAY2
ARRAY2
=> [:bar]
As such, it is common practice to immediately freeze values assigned to constants to fully deny any further changes and ensure that the original value is preserved (unless someone assigns a new value):
ARRAY3 = [:foo, :bar].freeze
ARRAY3 << :baz
# RuntimeError: can't modify frozen Array
A variable can change its value, it can vary.
A constant cannot change its value, it is constant.
In Ruby things are a bit more complex though. You can reassign the value of constants, but it will print a warning. This is meant to be used for debugging only and the general principle still applies that constants are meant to be used for values that never change.
In Ruby, a constant is an identifier that starts with a capital letter; it is intended to be assigned only once. You can reassign a constant, but you should not. Doing so will generate a warning:
NAME = "Fred"
NAME = "Barney" # => Warning: Already initialized constant NAME
A variable is an identifier that does not start with a capital letter; it may be assigned to more than once:
name = "Fred"
name = "Barney" # => No warning
When you create a class, a constant is created with the same name as the class; that constant is bound to the class:
class Foo
end
This is equivalent to this code which creates a new anonymous class and assigns it to the constant Foo:
Foo = Class.new do
end
You can reassign the constant identifier Foo, as you can with any other constant, but of course you shouldn't, and you will still get the warning:
Foo = 123 # => Already initialized constant Foo

Ruby "CONSTANTS" seem to be INVISIBLY ALTERABLE?

I understand that "constants" in Ruby are by convention called constants but are in fact mutable. However I was under the impression that when they were "mutated" that there was a warning:
class Z2
M = [0,1]
end
Z2::M # => [0, 1]
Z2::M = [0,3]
(irb):warning: already initialized constant Z2::M
(irb):warning: previous definition of M was here
However I found this is not the case all the time:
a = Z2::M
a[1] = 2
Z2::M # => [0,2] and no warning
Is this a gap in the "warning" system? I am inferring that assignment of a constant would duplicate it, but I guess that is not true either as it appears that constants and variables point to the same object? Does this mean that all so-called "constants" need to be frozen in order to prevent them from being changed without warning?
TL;DR
Short of monkey-patching Kernel#warn (see https://stackoverflow.com/a/662436/1301972) to raise an exception, you won't be able to prevent reassignment to the constant itself. This is generally not a pragmatic concern in idiomatic Ruby code where one expects to be able to do things like reopen classes, even though class names are also constants.
A Ruby constant isn't actually immutable, and you can't freeze a variable. However, you can get an exception to be raised when something attempts to modify the contents of a frozen object referenced by the constant.
Freezing Objects Deeply with Plain Ruby
Freezing an Array is easy:
CONSTANT_ONE = %w[one two three].freeze
but the strings stored in this Array are really references to String objects. So, while you can't modify this Array, you can still (for example) modify the String object referenced by index 0. To solve this problem, you need to freeze not just the Array, but the objects it holds, too. For example:
CONSTANT = %w[one two three].map(&:freeze).freeze
CONSTANT[2] << 'four'
# RuntimeError: can't modify frozen String
CONSTANT << 'five'
# RuntimeError: can't modify frozen Array
Freezing Objects Recursively with a Gem
Since freezing recursive references can be a bit unwieldy, it's good to know there's a gem for that. You can use ice_nine to deep-freeze most objects:
require 'ice_nine'
require 'ice_nine/core_ext/object'
OTHER_CONST = %w[a b c]
OTHER_CONST.deep_freeze
OTHER_CONST << 'd'
# RuntimeError: can't modify frozen Array
OTHER_CONST[2] = 'z'
# RuntimeError: can't modify frozen Array
A Better Way to Use Ruby Constants
Another option to consider is calling Object#dup when assigning the value of a constant to another variable, such as instance variables in your class initializers, in order to ensure you don't mutate your constant's references by accident. For example:
class Foo
CONSTANT = 'foo'
attr_accessor :variable
def initialize
#variable = CONSTANT.dup
end
end
foo = Foo.new
foo.variable << 'bar'
#=> "foobar"
Foo::CONSTANT
#=> "foo"
There is no gap, as you are not altering a constant. And the fact is that Ruby constants are just variables with extra warnings.
Constant, just as every variable, is merely a pointer to the object in memory. When you doM = [0,3] you are creating a new array and re-pointing constant to this new object, which triggers a warning.
However, when you run M[0] = 1 you are just modifying referenced object, but you do not change the constant, as it still points to the same object.
Important thing to realize here is that all classes in Ruby are just objects in memory, referenced with constants, so when you do:
class Z2
end
it is equivalent to (if Z2 is not defined or is not pointing onto a class object already):
Z2 = Class.new
Naturally class is a very dynamic object, as we keep adding methods to it and so on - we definitively don't want this to trigger any warnings.
If you do Z2::M[1] = 2 you won´t get the message either. I believe the lack of warning occours because you are changing the Array itself and not the reference Z2::M.
If M was an integer, for exemple:
class Z2
M = 1
end
a = Z2::M
a = 2
a # => 2
Z2::M # => 1
To modify an Array from a constant without modify the original you can do:
class Z2
M = [0,1]
end
a = Z2::M.dup
a[0] = 1
a # => [1,1]
Z2::M # => [0,1]

Plus equals with ruby send message

I'm getting familiar with ruby send method, but for some reason, I can't do something like this
a = 4
a.send(:+=, 1)
For some reason this doesn't work. Then I tried something like
a.send(:=, a.send(:+, 1))
But this doesn't work too. What is the proper way to fire plus equals through 'send'?
I think the basic option is only:
a = a.send(:+, 1)
That is because send is for messages to objects. Assignment modifies a variable, not an object.
It is possible to assign direct to variables with some meta-programming, but the code is convoluted, so far the best I can find is:
a = 1
var_name = :a
eval "#{var_name} = #{var_name}.send(:+, 1)"
puts a # 2
Or using instance variables:
#a = 2
var_name = :#a
instance_variable_set( var_name, instance_variable_get( var_name ).send(:+, 1) )
puts #a # 3
See the below :
p 4.respond_to?(:"+=") # false
p 4.respond_to?(:"=") # false
p 4.respond_to?(:"+") # true
a+=1 is syntactic sugar of a = a+1. But there is no direct method +=. = is an assignment operator,not the method as well. On the other hand Object#send takes method name as its argument. Thus your code will not work,the way you are looking for.
It is because Ruby doesn't have = method. In Ruby = don't work like in C/C++ but it rather assign new object reference to variable, not assign new value to variable.
You can't call a method on a, because a is not an object, it's a variable, and variables aren't objects in Ruby. You are calling a method on 4, but 4 is not the thing you want to modify, a is. It's just not possible.
Note: it is certainly possible to define a method named = or += and call it, but of course those methods will only exist on objects, not variables.
class Fixnum
define_method(:'+=') do |n| self + n end
end
a = 4
a.send(:'+=', 1)
# => 5
a
# => 4
This might miss the mark a bit, but I was trying to do this where a is actually a method dynamically called on an object. For example, with attributes like added_count and updated_count for Importer I wrote the following
class Importer
attr_accessor :added_count, :updated_count
def increment(method)
send("#{method}=", (send(method) + 1))
end
end
So I could use importer.increment(:added_count) or importer.increment(:updated_count)
Now this may seem silly if you only have these 2 different counters but in some cases we have a half dozen or more counters and different conditions on which attr to increment so it can be handy.

ruby and references. Working with fixnums

I know a bit about ruby way to handle objects and references. The replace stuff, ect ...
I know it d'ont work on fixnum, cause the var is the fixnum. But i wish to change the value of a fixnum inside a function, and that the value changed in the ouside var.
How can i do this ?
I guess i can use a string like this "1" but that's quite dirty.
Ruby will always pass-by-reference (because everything is an object) but Fixnum lacks any methods that allow you to mutate the value. See "void foo(int &x) -> Ruby? Passing integers by reference?" for more details.
You can either return a value that you then assign to your variable, like so:
a = 5
def do_something(value)
return 1 #this could be more complicated and depend on the value passed in
end
a = do_something(a)
or you could wrap your value in an object such as a Hash and have it updated that way.
a = {:value => 5}
def do_something(dict)
dict[:value] = 1
end
do_something(a) #now a[:value] is 1 outside the function
Hope this helps.
You could pass an array with a single number, like [1], or a hash like {value: 1}. Less ugly than a string, as your number itself remains a number, but less overhead than a new class...
When I was building a game I had the same problem you have. There was a numeric score that represented how many zombies you've killed and I needed to manually keep it in sync between Player (that incremented the score), ScoreBar and ScoreScreen (that displayed the score). The solution I've found was creating a separate class for the score that will wrap the value and mutate it:
class Score
def initialize(value = 0)
#value = value
end
def increment
#value += 1
end
def to_i
#value
end
def to_s
#value.to_s
end
end

Why don't numbers support .dup?

>> a = 5
=> 5
>> b = "hello, world!"
=> "hello, world!"
>> b.dup
=> "hello, world!"
>> a.dup
TypeError: can't dup Fixnum
from (irb):4:in `dup'
from (irb):4
I understand that Ruby will make a copy every time you assign an integer to a new variable, but why does Numeric#dup raise an error?
Wouldn't this break abstraction, since all objects should be expected to respond to .dup properly?
Rewriting the dup method will fix the problem, as far as I can tell:
>> class Numeric
>> def dup()
>> self
>> end
>> end
Does this have a downside I'm not seeing? Why isn't this built into Ruby?
Most objects in Ruby are passed by reference and can be dupped. Eg:
s = "Hello"
t = s # s & t reference to the same string
t.upcase! # modifying either one will affect the other
s # ==> "HELLO"
A few objects in Ruby are immediate, though. They are passed by value, there can only be one of this value and it therefore cannot be duped. These are any (small) integers, true, false, symbols and nil. Many floats are also immediates in Ruby 2.0 on 64 bit systems.
In this (preposterous) example, any "42" will hold the same instance variable.
class Fixnum
attr_accessor :name
alias_method :original_to_s, :to_s
def to_s
name || original_to_s
end
end
42.name = "The Answer"
puts *41..43 # => 41, The Answer, 43
Since you would normally expect something.dup.name = "new name" to not affect any other object than the copy obtained with dup, Ruby chooses not to define dup on immediates.
Your question is more complex than it appears. There was some discussion on ruby-core as to how this can be made easier. Also, other types of Numeric objects (floats, bignums, rationals and complex numbers) can not be duped although they are not immediates either.
Note that ActiveSupport (part of rails) provide the method duplicable? on all objects
The problem with the dup() function that you defined is that it doesn't return a copy of the object, but rather returns the object itself. This is not what a duplicate procedure is supposed to do.
I don't know Ruby, but a possible reason I can think of for dup not being defined for numbers is that a number is a basic type and thus, doing something like:
>> a = 5
>> b = a
would automatically assign the value 5 into the variable b, as opposed to making b and a point to the same value in memory.

Resources