My Journey to learn about Constant in Ruby
Oh my constant!
I have been writing Ruby code for two years. There has always been a mystery for me: the Constant in Ruby. Wait, Constant? Yes, Constant! This is the usual way I have created a constant in Ruby.
irb(main):001> FOO = "abc".freeze
=> "abc"
Nothing too crazy, right? I simply created a constant ‘FOO’ and assigned the frozen (which means immutable in Ruby) value ‘abc’ to it. I followed the convention in Ruby that constant variable names should start with an uppercase letter. However, I came across something surprising: Ruby allows the reassignment of a new value to a constant that has already been initialized. Look at the example to better understand what I’m talking about!
irb(main):001> FOO = "abc".freeze
=> "abc"
irb(main):002> FOO = "efg".freeze
(irb):2: warning: already initialized constant FOO
(irb):1: warning: previous definition of FOO was here
=> "efg"
irb(main):003> FOO
=> "efg"
As you can see, the constant ‘FOO’ has the value ‘efg’ not ‘abc’ now. It throws a warning but not an error when the constant gets reassigned. Ruby does not completely block the reassignment of the constant! The example above shows that Ruby violates the concept of Constant within my knowledge. If this is permitted, then can it truly be considered as constant? This shook my entire programming knowledge, so I searched for the terminology ‘Constant,’ which is commonly used in the programming world.
Here is what Wikipedia says about Constant!
“For software engineers, the concept of a constant is commonly understood as a value that remains unaltered by the program during normal execution.” - Wikipedia
Okay then, I shouldn’t be the only one confused by this. Ruby violates this common sense of Constant in the programming world.
Frozen vs Unfrozen
Wait… I have another interesting thing to share here. Please see these two example code snippets below.
irb(main):001> FOO = "abc"
=> "abc"
irb(main):002> FOO.sub! "a", "z"
=> "zbc"
irb(main):003> FOO
=> "zbc"
irb(main):001> FOO = "abc".freeze
=> "abc"
irb(main):002> FOO.sub! "a", "z"
(irb):2:in `sub!': can't modify frozen String: "abc" (FrozenError)
irb(main):003> FOO
=> "abc"
The first example substitutes ‘a’ of the unfrozen string ‘abc’ with ‘z’. You can see that ‘FOO’ has ‘zbc’ in the end.
In the second example, it attempts to substitute ’a’ of the frozen string ’abc’ with ’z’, essentially trying to modify the referenced object, which is immutable. However, we can observe a different result here. The second example fails to substitute and throws a Frozen error with the message “can’t modify frozen String: abc”.
Because of the result of the second example, I felt that Ruby does not completely violate the common concept of Constant.
However, the question still lingers in my mind: What’s the point of throwing the Frozen error when it already allows the reassignment of Constant? Once our code loses references to the assigned object through reassignment, we lose the pointer to the object. So, what is the point of making it immutable? It will be garbage collected anyway? This starts giving me a headache…
Matz is here to answer!
Surprisingly, there was a person who had the exact same question as me in Quora, and the founder of Ruby “Matz (Yukihiro Matsumoto)” left the answer to this question!! (Link to the Q&A)
The Q&A is in Japanese, so I used Google Translator to translate it into English.
Question: Why is it in Ruby that constants can be reassigned, and you have to use freeze if you want them to be immutable? What was the reason for not making them immutable from the beginning?
Answer (Matz): This question confuses two roles that the term “constant” is believed to indicate, and in Ruby, these two roles are distinctly different.
The first role is that the value (object) being referenced does not change. In Ruby, constants only have this role, and names starting with uppercase letters are considered constants, meaning their reference cannot be changed. The part of the question stating “reassignable and not immutable from the beginning” indicates this role.
Indeed, this is true. In Ruby, while a warning is issued when attempting to reassign a constant, one can ignore it and still overwrite the constant’s value. The reason for this behavior is the anticipation that in using Ruby for embedded applications, such as developing an editor with Ruby embedded, if constants defined in configuration files written in Ruby were immutable and caused errors, the usability of these configuration files would be compromised. Therefore, despite being constants, the decision was made to allow potential reassignment while keeping warnings as a gentleman’s agreement.
The other meaning of constants is that the reference does not change. While C’s const also holds this meaning, in Ruby, where many objects are mutable, constants do not possess this role. If you wish to prohibit the alteration of the object referenced by a constant, you need to explicitly use freeze. This aspect is unrelated to Ruby’s notion of “constants”.
Wow, things just got so much clearer, right?!
Key Learnings
- Constant in Ruby is not supposed to be reassigned. Considering the variety of Ruby usage, the design decision was made to choose a warning instead of an error.
- Use ‘freeze’ if you want to make the referenced object immutable. However, that is a separate concept from Constant in Ruby. It simply makes an object immutable and the usage is not limited to constant.
Conclusion
So, I mistakenly mixed and used two different concepts together. Making constants without thinking led me to treat these two things as one concept in Ruby! At least, now I know! However, his design decision on Constant, specifically regarding reassignment, still made me think more. I came across the following statement mentioned by Matz and I suppose his philosophy also influences this design decision regarding constants.
Matz: Language designers want to design the perfect language. They want to be able to say, “My language is perfect. It can do everything.” But it’s just plain impossible to design a perfect language, because there are two ways to look at a language. One way is by looking at what can be done with that language. The other is by looking at how we feel using that language—how we feel while programming. - “The Philosophy of Ruby A Conversation with Yukihiro Matsumoto, Part I”. (Check the link! You will get a chance to learn deeply about Ruby and his philosophy on programming languages!)
Also, I believe his love for Emacs, which motivated and influenced him to create Ruby, likely influenced this decision as well. (Here is the link to learn about his love for Emacs: “How Emacs changed my life”)