Ruby Koan: Constants Become Symbols

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



Leave a reply



Submit