Руби Коанс: зачем преобразовывать список символов в строки


86

Я имею в виду этот тест в about_symbols.rb в Ruby Koans https://github.com/edgecase/ruby_koans/blob/master/src/about_symbols.rb#L26

def test_method_names_become_symbols
  symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s }
  assert_equal true, 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?

Почему мы должны сначала преобразовать этот список в строки?

Ответы:


112

Это связано с тем, как работают символы. Для каждого символа фактически существует только один из них. За кулисами символ - это просто число, на которое ссылается имя (начинающееся с двоеточия). Таким образом, сравнивая равенство двух символов, вы сравниваете идентичность объекта, а не содержимое идентификатора, который ссылается на этот символ.

Если бы вы провели простой тест : test == "test" , он будет ложным. Итак, если вы собираете все символы, определенные до сих пор, в массив, вам нужно будет сначала преобразовать их в строки, прежде чем сравнивать их. Вы не можете сделать это наоборот (сначала преобразовать строку, которую хотите сравнить, в символ), потому что это приведет к созданию единственного экземпляра этого символа и «загрязнению» вашего списка символом, существование которого вы проверяете.

Надеюсь, это поможет. Это немного странно, потому что вам нужно проверить наличие символа, не создавая его случайно во время теста. Обычно вы не видите такого кода.


2
Обратите внимание, что лучший способ сделать это безопасно - назначить вывод Symbol.all_symbolsпеременной, а затем проверить на включение. Символы быстрее сравниваются, и вы избегаете преобразования тысяч символов в строки.
Coreyward

4
У него все еще есть проблема создания символа, который нельзя уничтожить. Любые будущие испытания этого символа будут провалены. Но это всего лишь коан, он не должен иметь особого смысла или быть быстрым, просто продемонстрируйте, как работают символы.
AboutRuby

2
Этот ответ мне не подходит. Если мы ищем наличие символа, почему мы указываем строковый аргумент, include?если мы указали, :test_method_names_become_symbolsнам не нужно было бы преобразовывать все эти символы в строки.
Исаак Рабинович

3
Исаак, из-за выявленной проблемы, заключающейся в том, что указание :test_method_names_become_symbolsв сравнении создаст ее, поэтому сравнение всегда будет истинным. Преобразовывая all_symbolsв строки и сравнивая строки, мы можем определить, существовал ли символ до сравнения.
Стивен

3
Я так многого не понимаю. Если я делаю >> array_of_symbols = Symbol.all_symbols, затем >> array_of_symbols.include? (: Not_yet_used), я получаю false, и я получаю true для чего-то, что было определено, поэтому я не понимаю, почему преобразование в строки необходимо .
codenoob 01

75

Потому что, если вы это сделаете:

assert_equal true, all_symbols.include?(:test_method_names_become_symbols)

Это может (в зависимости от вашей реализации ruby) автоматически быть истинным, потому что :test_method_names_become_symbolsсоздает символ. См. Этот отчет об ошибке .


1
Так что принятый ответ неправильный (или мне так кажется).
Исаак Рабинович

3
Исаак, другой ответ не является неправильным, но он не объясняется так кратко. для уверенности. В любом случае, вы можете проверить, что говорит Эндрю, с помощью следующего: assert_equal true, Symbol.all_symbols.include? (: Abcdef) Это всегда будет проходить (по крайней мере, для меня) независимо от символа. Думаю, один урок состоит в том, что не пытайтесь использовать наличие / отсутствие символов в качестве логического флага.
Стивен

1
Глядя на этот вопрос, который все еще интересует меня 2 года спустя, я очень ценю этот ответ и комментарии. Я думаю, что Исаак прав, это ответ, наиболее четко объясняющий, почему преобразование в строки может быть подходящим вариантом, хотя я думаю, что вы могли бы добавить промежуточный шаг (сохранить все символы перед сравнением), чтобы он работал лучше.
codenoob

4

Оба приведенных выше ответа верны, но в свете вопроса Картика выше я подумал, что опубликую тест, который показывает, как можно точно передать символ includeметоду.

def test_you_create_a_new_symbol_in_the_test
  array_of_symbols = []
  array_of_symbols << Symbol.all_symbols
  all_symbols = Symbol.all_symbols.map {|x| x}
  assert_equal false, array_of_symbols.include?(:this_should_not_be_in_the_symbols_collection)  #this works because we stored all symbols in an array before creating the symbol :this_should_not_be_in_the_symbols_collection in the test
  assert_equal true, all_symbols.include?(:this_also_should_not_be_in_the_symbols_collection) #This is the case noted in previous answers...here we've created a new symbol (:this_also_should_not_be_in_the_symbols_collection) in the test and then mapped all the symbols for comparison. Since we created the symbol before querying all_symbols, this test passes.
end

Дополнительное примечание о Коанах: используйте putsинструкции, а также специальные тесты, если вы ничего не понимаете. Например, если вы видите:

string = "the:rain:in:spain"
words = string.split(/:/)

и понятия не имею, что wordsможет быть, добавьте строку

puts words

и запустить rakeв командной строке. Точно так же тесты, подобные тому, который я добавил выше, могут быть полезны с точки зрения понимания некоторых нюансов Ruby.


Глядя на этот вопрос, который все еще интересует меня 2 года спустя, я очень ценю этот ответ и комментарии.
codenoob
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.