Ruby Koans #75 test_constants_become_symbols, corr

2019-04-21 07:31发布

My question builds upon this question: Ruby Koan: Constants become symbols. I have the following code:

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 __, all_symbols.include?(__)
  end
end

Is the correct answer supposed to be the following?

    assert_equal true, all_symbols.include?("RubyConstant".to_sym)

I know I shouldn't just do this:

    assert_equal true, all_symbols.include?(:RubyConstant)

because then I could put anything in there and it would still be true

    assert_equal true, all_symbols.include?(:DoesNotMatter)

Apologies in advance for asking simple a "yes or no" question. I was curious as to know what the "right" answer is. I would have preferred to just ask this question in the comments in the previous post I mentioned above but I couldn't without making a separate post.

标签: ruby symbols
3条回答
Anthone
2楼-- · 2019-04-21 08:05

Symbol.all_symbols contains every symbol that was referenced - variable names, class names, constant names, actual symbols. What actually this variable contains is implementation defined, but in Ruby MRI, many symbols are already on this list.

irb(main):001:0> Constant = 42
=> 42
irb(main):002:0> Symbol.all_symbols
=> [:"", :"<IFUNC>", :"<CFUNC>", :respond_to?,  ..., :irb_exit_org, :Constant]

But now, there is a catch.

Symbol.all_symbols.include?(:DoesNotMatter)

Before you will run this code, :DoesNotMatter doesn't exist in all_symbols, but it somehow still exists. Well, actually when you use symbol literal, it's inserted to Symbol.all_symbols (unless it's there already). So, the symbol is already here before you even call .include?.

EDIT: Gregory Brown suggested following workaround. It works because in Ruby assignment of Symbol.all_symbols copies variable for some reason instead of copying the reference to variable.

irb(main):001:0> symbols = Symbol.all_symbols; 1
=> 1
irb(main):002:0> symbols.include? :something
=> false
irb(main):003:0>
查看更多
Animai°情兽
3楼-- · 2019-04-21 08:06

Here's what I got:

in_ruby_version("mri") do
  RubyConstant = "What is the sound of one hand clapping?"
  def test_constants_become_symbols
    all_symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s }

    assert_equal true, all_symbols_as_strings.include?("RubyConstant")
  end
end
查看更多
Explosion°爆炸
4楼-- · 2019-04-21 08:16

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.

查看更多
登录 后发表回答