2011-11-26 133 views
3

Ruby Koans的帮助下尝试Ruby。有下面的测试有:使用include检查符号是否存在于数组中?

def test_method_names_become_symbols 
    symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s } 
    assert_equal __, symbols_as_strings.include?("test_method_names_become_symbols") 
end 

# THINK ABOUT IT: 
# 
# Why do we convert the list of symbols to strings and then compare 
# against the string value rather than against symbols? 

我试图做同样的事情在IRB控制台,它为未定义的方法返回false。但之后我在test.rb文件中尝试了相同的操作,并且true已返回到现有和未知方法。

示例代码:

def test_method 
end 
symbols = Symbol.all_symbols.map { |x| x } 
puts symbols.include?(:test_method) # returns true in both cases 
puts symbols.include?(:test_method_nonexistant) # returns false in irb, true if executed directly 

的问题是:为什么我们的符号转换为字符串,在这种情况下,为什么有在IRB和正常的文件不同的结果?

谢谢!

+0

[Ruby Koans:为什么要将符号列表转换为字符串](http://stackoverflow.com/questions/4686097/ruby-koans-why-convert-list-of-symbols-to-strings) –

回答

7

让我们通过你的测试代码稍加修改的版本,因为它是由irb并作为一个独立的脚本看出:

def test_method;end 
symbols = Symbol.all_symbols # This is already a "fixed" array, no need for map 
puts symbols.include?(:test_method) 
puts symbols.include?('test_method_nonexistent'.to_sym) 
puts symbols.include?(:test_method_nonexistent) 
eval 'puts symbols.include?(:really_not_there)' 

当您尝试这irb,每一行都会被分析和评估在下一行之前。当你点击第二行时,symbols将包含:test_method,因为def test_method;end已经被评估。但是,当我们击中第2行时,:test_method_nonexistent符号在任何地方都没有出现过,所以第4行和第5行会说“错误”。第6行当然会给我们另一个错误,因为:really_not_thereeval返回后才会存在。所以irb这样说:

true 
false 
false 
false 

如果你运行这个作为一个Ruby脚本,事情发生在略微不同的顺序。第一个Ruby将脚本解析为Ruby VM可以理解的内部格式,然后返回到第一行并开始执行脚本。在解析脚本时,解析第一行后将存在:test_method符号,解析第五行后将存在:test_method_nonexistent;所以,在脚本运行之前,我们感兴趣的两个符号是已知的。当我们击中第六行时,Ruby只会看到一个eval和一个字符串,但它还不知道eval会导致一个符号出现。

现在,我们有我们的两个符号(:test_method:test_method_nonexistent)和一个简单的字符串,送入eval时,将创建一个符号(:really_not_there)的。然后我们回到开始,VM开始运行代码。当我们运行第2行并缓存我们的符号数组时,:test_method:test_method_nonexistent都将存在并出现在symbols数组中,因为解析器创建了它们。所以第3行到第5行:

puts symbols.include?(:test_method) 
puts symbols.include?('test_method_nonexistent'.to_sym) 
puts symbols.include?(:test_method_nonexistent) 

将打印“true”。然后我们打线6:因为:really_not_thereeval在运行时,而不是在分析过程中产生

eval 'puts symbols.include?(:really_not_there)' 

和“假”被打印出来。其结果是,红宝石说:

symbols = Symbol.all_symbols 
puts symbols.include?('really_not_there'.to_sym) 

然后,我们会得到另一个“真”走出两个irb和独立脚本,因为eval

true 
true 
true 
false 

如果我们在末尾添加这将创建:really_not_there,我们将抓取符号列表的新副本。

+0

非常棒,非常感谢您的详细解释! –

+1

对常规口译人员的不同行为进行细致分析。 +1 – coreyward

2

当检查符号的存在时,必须将符号转换为字符串的原因是否则它将始终返回true。传递给include?方法的参数首先被评估,所以如果你传递一个符号,一个新的符号被实例化并添加到堆中,所以Symbol.all_symbols实际上具有该符号的副本。

Symbol.all_symbols.include? :the_crow_flies_at_midnight #=> true 

然而,转换数千符号为字符串进行比较(这是与符号快得多)是不良的溶液。更好的方法是改变顺序,这些声明得到评估:

symbols = Symbol.all_symbols 
symbols.include? :the_crow_flies_at_midnight #=> false 

这个“快照”的是什么符号在字典中我们测试的符号插入到堆上前服用,所以尽管我们的观点在调用include?方法时存在于堆上,我们正在获得我们期望的结果。

我不知道它为什么不能在IRB控制台中工作。也许你打错了。

+0

谢谢你,这很有道理。但为什么这个新符号会出现在我的'symbols'数组中。它在某个时候形成,为什么它会改变?另外,我确定我没有错误输入,你可以自己尝试。 –