在Ruby Koans的帮助下尝试Ruby。那里有以下测试:
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 :(得分: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行会说“false”。当然,第6行会给我们另一个错误,因为在:really_not_there
返回之前eval
不存在。所以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行:
eval 'puts symbols.include?(:really_not_there)'
打印和“false”是因为:really_not_there
是在运行时由eval
创建的,而不是在解析期间创建的。结果是Ruby说:
true
true
true
false
如果我们在最后添加:
symbols = Symbol.all_symbols
puts symbols.include?('really_not_there'.to_sym)
然后,我们将从irb
和独立脚本中获得另一个“真实”,因为eval
将创建:really_not_there
,我们将抓取一个新的副本符号列表。
答案 1 :(得分: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控制台中不起作用。也许你错了。