Рубі Коанс: навіщо перетворювати список символів у рядки


85

Я маю на увазі цей тест у 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?

Чому саме нам спочатку потрібно перетворити цей список на рядки?

Відповіді:


110

Це пов’язано з тим, як працюють символи. Для кожного символу насправді існує лише один із них. За лаштунками символ - це просто цифра, на яку позначається ім'я (починаючи з двокрапки). Таким чином, порівнюючи рівність двох символів, ви порівнюєте ідентичність об’єкта, а не вміст ідентифікатора, який посилається на цей символ.

Якщо ви робили простий тест : 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

75

Тому що якщо ви це зробите:

assert_equal true, all_symbols.include?(:test_method_names_become_symbols)

Це може (залежно від вашої реалізації рубіну) автоматично бути істинним, оскільки :test_method_names_become_symbolsстворює символ. Дивіться цей звіт про помилки .


1
Отже, прийнята відповідь - неправильна (або мені так здається).
Ісаак Рабінович

3
Ісаак, інша відповідь не хибна, але вона пояснюється не так стисло. напевно. У будь-якому випадку, ви можете перевірити, що говорить Ендрю, наступним чином: assert_equal true, Symbol.all_symbols.include? (: Abcdef) Це завжди буде проходити (принаймні, це стосується мене) незалежно від символу. Думаю, один урок - не намагайтеся використовувати наявність / відсутність символів як логічний прапор.
Стівен

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

3

Обидві відповіді вище є правильними, але у світлі вищезазначеного запитання Картіка я думав, що опублікую тест, який ілюструє, як можна точно передавати символ 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

Додаткова примітка про Koans: використовуйте 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.