Ruby Koan: Constants become symbols
hoha has it right but I'll try to expand and clarify a bit.
The interpreter will create the :nonexistent
symbol when it parses test_constants_become_symbols
. Then, when you run it, Symbol.all_symbols
is called to get a list of all known symbols and :nonexistent
is in the list. Also note that the double quotes on "nonexistent"
are a syntax issue rather than an internal representation issue so :nonexistent
and :"nonexistent"
are the same thing.
If you comment out this one:
assert_equal true, all_symbols.include?(:"What is the sound of one hand clapping?")
then the :"What is the sound of one hand clapping?"
symbol will not be seen by the parser and so it won't be in the all_symbols
array. The .to_sym
method call on the following line is executed when test_constants_become_symbols
is executed; so, the :"What is the sound of one hand clapping?"
symbol is created after you get your all_symbols
and this will fail:
assert_equal true, all_symbols.include?("What is the sound of one hand clapping?".to_sym)
If you execute test_constants_become_symbols
again in the same interpreter instance (with the second assert_equal
still commented out) then both uncommented assert_equal
calls will pass as the first run through test_constants_become_symbols
will create the :"What is the sound of one hand clapping?"
and the second Symbol.all_symbols
will include it in the returned array.
Running your code in irb
without wrapping it in a def
might help you see what's going on.
Ruby Koans #75 test_constants_become_symbols, correct answer?
NOTE: the following answer only applies to environments like irb, where Ruby code is being executed line by line. When executing code in a file, Ruby scans the entire file for symbols before executing anything, so the following details are not accurate. I've not deleted this answer because it exposes an interesting edge case, but see @GlichMr's answer for a better explanation of the problem.
You can safely do the following, because Symbol.all_symbols
returns a copy of the array of symbols, not a reference.
assert_equal true, all_symbols.include?(:RubyConstant)
I think that is the intended answer to the koan, and it's why all_symbols
is defined rather than calling Symbol.all_symbols
directly. For some evidence, see the following:
>> X = 1
=> 1
>> all_symbols = Symbol.all_symbols; nil
=> nil
>> Y = 2
=> 2
>> all_symbols.include?(:X)
=> true
>> all_symbols.include?(:Y)
=> false
Using String#to_sym
would make it possible to make these calls against Symbol.all_symbols
directly, but is not necessary for solving this koan.
What to learn from Ruby Koan nº 75?
In ruby, variables that start with capital letters become constants. The goal of the koan is to teach you that constants become symbols in ruby and get added to the ruby's symbols table.
in_ruby_version("mri") do
RubyConstant = "What is the sound of one hand clapping?"
def test_constants_become_symbols
all_symbols = Symbol.all_symbols
assert_equal true, all_symbols.include?("RubyConstant".to_sym)
end
end
Also notice how it says "RubyConstant".to_sym
instead of :RubyConstant
. It's to avoid confusion since the ruby interpreter will automatically create a symbol when it parses a ruby function, as explained here.
Ruby Koans: Why convert list of symbols to strings
This has to do with how symbols work. For each symbol, only one of it actually exists. Behind the scenes, a symbol is just a number referred to by a name (starting with a colon). Thus, when comparing the equality of two symbols, you're comparing object identity and not the content of the identifier that refers to this symbol.
If you were to do the simple test :test == "test", it will be false. So, if you were to gather all of the symbols defined thus far into an array, you would need to convert them to strings first before comparing them. You can't do this the opposite way (convert the string you want to compare into a symbol first) because doing that would create the single instance of that symbol and "pollute" your list with the symbol you're testing for existence.
Hope that helps. This is a bit of an odd one, because you have to test for the presence of a symbol without accidentally creating that symbol during the test. You usually don't see code like that.
Ruby koan 124 - Are these symbols spontaneously changing type?
No, symbols cannot change type on their own, but when you're using one
or two
in your example, you are using variables with similar names, provided by Ruby based on your method definition, not the symbols themselves.
Consider a hash: when you have a hash a = { one: 1, two: '1234' }
and you write a[:one]
you don't get a symbol, but an appropriate value. So :one
still is a symbol, but with [a[:one], a[:two]]
you will get [1, '1234']
array, not [:one, :two]
.
Ruby Send Method, iterating as symbols instead of strings
Obviously, the two versions are not equivalent. The first one will call a method whose name is based on the content of the variable k
. In the second version, the variable k
is never used, it will simply call the method k
over and over and over again.
IOW: the first version will call a different method on each iteration of the loop, the second one will call the same method on every iteration of the loop.
You can, of course, use symbols in exactly the same way you use strings here:
def initialize(attributes = {})
attributes.each do |k,v|
self.send(:"#{k}=", value)
end
end
RubyKoans: broken koan?
Oh, I tested this koan. The error is on line 21 if you noticed that, not the "test_calling_global_methods_without_parentheses" method. It's the "test_sometimes_missing_parentheses_are_ambiguous" method goes wrong as it should be. You are expected to correct that method.
def test_calling_global_methods_without_parentheses
result = my_global_method 2, 3
assert_equal 5, result # You're fine with this koan.
end
# (NOTE: We are Using eval below because the example code is
# considered to be syntactically invalid).
def test_sometimes_missing_parentheses_are_ambiguous
eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK
# **LOOK HERE~~~ HERE IS THE ERROR YOU SEE** Just correct it.
And if there is any koan you don't know how to deal with, just comment it.
Why can I change constants?
Well, constants in Ruby are relatively variable. Objects they point to can be swapped (as in your example) and their state can be changed as well.
class TestClass
Constant = []
end
TestClass::Constant << "no warning at all!"
The only advantage they provide are warnings generated when you make an existing constant point to another object. See "Programming Ruby", section "Variables and Constants". It's old but still valid.
The purpose for Ruby's constants to exist is signalling that a given reference shouldn't be changed. For instance, if you do Math::PI = 3.0
you deserve to be warned.
Theoretically, you could break compatibility with the original implementation and enforce constants' immutability. As a result you could achieve a slight performance improvement thanks to optimised method dispatching.
In the example above you'd know that Constant.is_a? Array
so dispatching the <<
symbol to the actual method could be done only once, on the first execution of that line. The problem is that Ruby enables you to redefine Array#<<
thus making the problem more tricky.
Checking whether various Ruby implementations try to use such optimisation would require some additional research and digging in their documentation or sources.
Related Topics
How to Create an Array in Ruby with Default Values
Rails Erb Form Helper Options_For_Select :Selected
Rails3 Devise Undefined Method 'Confirmation_Url'
Why Alias_Method Fails in Rails Model
Options for Distribution of an Offline Ruby on Rails Application
What Does :: (Double Colon) Mean in Ruby
Rails 3:How to Generate Models for Existing Database Tables
Error While Trying to Load the Gem 'Devise. Activesupport: Duration Can't Be Coerced into Integer
Active Admin - Refresh Second Drop Down Based on First Drop Down, Ruby on Rails
Printing an Ascii Spinning "Cursor" in the Console
Run Ruby Script in Elevated Mode
Changing Field Separator/Delimiter in Exported CSV Using Ruby CSV
Apply Method to Each Elements in Array/Enumerable