使用include检查数组中是否存在符号?

时间:2011-11-26 19:48:26

标签: ruby symbols

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和普通文件中有不同的结果?

谢谢!

2 个答案:

答案 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控制台中不起作用。也许你错了。