What is the difference between a constant and a variable in Ruby? - 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

Related

Ruby immutability of strings and symbols (What if we store them in variables)

A string is a primitive type; whenever you call the string, it has a new object id. A symbol is a referenced type; whenever you create a symbol, you create a pointer, which points to the value.
I stored symbols in variables:
var1 = :foo
var1.object_id # => 2598748
:foo.object_id # => 2598748
var2 = :foo
var2.object_id # => 2598748
var2 = "hello"
var2.object_id # => 70131755422100
How is it possible that I create a second variable var2, and it has the same object id as var1? I create a second element. Does it mean that variables are also pointers?
Both variables point to the symbol :foo. The symbol :foo is stored just once, right?
Two variables are created, so they should be in the memory, and they cannot be in the same place because they have different names. var1 and var2 need to be stored, so that I can call them later. I don't get how I can call them if they have the same object id. If someone can help me to understand this, I'd be thankful.
Ruby variables are references to objects, so when you send a method to a variable, the object it references is the context in which it is evaluated. It's probably more clear to look at the first image in the top rated answer (below the accepted answer) here.
So, to figure out what's going on, let's dig into the documentation a bit and see what happens with your code snippet.
Ruby's Symbol class documentation:
https://ruby-doc.org/core-2.5.0/Symbol.html
Symbol objects represent names and some strings inside the Ruby interpreter. They are generated using the :name and :"string" literals syntax, and by the various to_sym methods. The same Symbol object will be created for a given name or string for the duration of a program's execution, regardless of the context or meaning of that name. Thus if Fred is a constant in one context, a method in another, and a class in a third, the Symbol :Fred will be the same object in all three contexts.
Ruby's Object#object_id documentation:
https://ruby-doc.org/core-2.5.1/Object.html#method-i-object_id
Returns an integer identifier for obj.
The same number will be returned on all calls to object_id for a given object, and no two active objects will share an id.
So here's what's happening step-by-step:
# We create two variables that refer to the same object, :foo
var1 = :foo
var2 = :foo
var1.object_id = 2598748
var2.object_id = 2598748
# Evaluated as:
# var1.object_id => :foo.object_id => 2598748
# var2.object_id => :foo.object_id => 2598748
As discussed in the first link above, Ruby is pass-by-value, but every value is an Object, so your variables both evaluate to the same value. Since every symbol made of the same string ("foo" in this case) refers to the same object, and Object#object_id always returns the same id for the same object, you get the same id back.

Dynamically Modify Ruby Instance Variable in Array

I have an array ["moniker", #moniker] where the moniker can be any one of around 100 instance variables and its string representation. I want to change what the instance variable located at index 1 is referencing (not that data itself, which may very well be immutable). Just doing array[1] = newData doesn't work because it just changes whats in the array. I know this would be simple in C, but I'm struggling to find a way to do this in Ruby.
Your struggle is because you are thinking like a C programmer, where you have access to the underlying pointers, and where everything is mutable. In C, The array would store a pointer to a mutable integer, and you could change the integer whenever you want. In Ruby, every variable is a reference to an object, and numbers are immutable objects. So, #moniker is a reference to an object, the integer 4. When you create the array, you copy that reference into the array, so now the integer 4 has two references: One from #moniker, and one from the array. As you have found, changing the reference in the array does not change the reference named #moniker--it still refers to the object 4.
"Box" a reference in an array
This is not really a Ruby way of doing things. I'm showing it because it might help to illustrate how Ruby works with references.
You can box a reference in an array:
#moniker = [4]
a = ["moniker", #moniker]
This requires you to deference the array when you want access to the underlying object:
#moniker.first
a[1].first
But now you can change the underlying integer in #moniker and the array will see the change:
#moniker[0] = 42
p a[1].first # => 42
Encapsulate the number in a mutable object.
Being an object oriented language, you might encapsulate that number in a mutable object.
class Moniker
attr_accessor :value
def initialize(value)
#value = value
end
end
(attr_accessor :value builds reader and writer methods for the instance variable #value).
#moniker = Moniker.new(4)
a = ["monikier", #moniker]
#moniker.value = 42
p a[1].value # => 42
You would obviously chose a better name than "value." I couldn't because I don't know what the value represents.
Why these two solutions work
This was a comment by Jörg W Mittag, but it deserves to be part of the answer:
It may seem obvious, but I wanted to mention it explicitly: the two solutions are the same solution. The first uses an already existing class with generic semantics, the the second defines a new class with precise semantics for the specific encapsulated value. But in both cases, it's about wrapping the immutable value in a mutable value and mutating the "outer" value.
#moniker never got into the array but its value did.
In IRB:
#moniker = 4
a = ["moniker", #moniker]
=> ["moniker", 4]
You're just working with the value in the array anyway so just change it and you're good to go:
a[1] = 5
a
=> ["moniker", 5]
You might want to consider a hash:
h = {:moniker => #moniker}
=> {:moniker=>4}
h[:moniker] = 5
h
=> {:moniker=>5}

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]

Ruby has no constant values?

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

Ruby hash value converts to string, don't know why

I have a 'strange' problem, the following code converts the location lat value into a string (With a + sign for each iteration) leading to an eventual exception when comparing values. I've tried the code with values for another location and it works fine. The only difference is that the other numbers were negatives.
location= {:lng => 2.0781,:lat => 41.2899}
while location[:lat] < top
sleep(1)
checkTweets(location)
bottom+=0.075
location[:lat] = bottom
end
The issue occurs before entering the check tweets location. The values for the hash are as follows
To to conclude, my question is can anyone explain to me why location[:lat] ends up being a string in this circumstance?
Bottom is initialized as 30.0400 which is assigned to the :lat value. The checkTweets method simply writes a file based on a mongodb query.
Right I found the solution to this. It was the twitter library which was turning the Hash float values into strings.
Am I wrong in assuming that the scope of the variable in the checkTweets method should not impact the location variable here, they are both declared in seperate methods, they are not class level.
I wrong in assuming that the scope of the variable in the checkTweets method should not impact the location variable here, they are both declared in seperate methods, they are not class level.
No, but variable scope is not the issue here. The location variable is local to your method and as such cannot be changed by the checkTweets method. That is correct.
However the object that is referenced by the location variable can be changed from the checkTweets method and that is exactly what happens (though I have to say that mutating arguments is very bad style).
A little example to illustrate reference semantics and mutation in ruby:
def f1(arr)
arr = [1,2,3] # Changes the variable arr, which is local to f1
# This change is not visible on the outside
end
def f2(arr)
arr.concat [1,2,3] # Changes the object that arr refers to
# This change will be visible any place where the same
# array is referenced
end
foo = [42,23]
f1(foo)
# foo is still [42, 23]
f2(foo)
# foo is now [42, 23, 1, 2, 3]
Here the variable foo hasn't been changed to refer to another object (that would not be possible from inside a method), but the object that foo refers to has been changed. The same happens in your checkTweets method.

Resources