Ruby Koans: Por que converter lista de símbolos em strings

86

Estou me referindo a este teste em about_symbols.rb em 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?

Por que exatamente temos que converter essa lista em strings primeiro?

codercode
fonte

Respostas:

112

Isso tem a ver com o funcionamento dos símbolos. Para cada símbolo, apenas um deles realmente existe. Nos bastidores, um símbolo é apenas um número referido por um nome (começando com dois pontos). Assim, ao comparar a igualdade de dois símbolos, você está comparando a identidade do objeto e não o conteúdo do identificador que se refere a este símbolo.

Se você fosse fazer o teste simples : test == "test" , seria falso. Portanto, se você fosse reunir todos os símbolos definidos até agora em um array, primeiro precisaria convertê-los em strings antes de compará-los. Você não pode fazer isso da maneira oposta (primeiro converta a string que deseja comparar em um símbolo) porque isso criaria uma única instância desse símbolo e "poluiria" sua lista com o símbolo que você está testando.

Espero que ajude. Isso é um pouco estranho, porque você tem que testar a presença de um símbolo sem criar acidentalmente esse símbolo durante o teste. Você geralmente não vê um código assim.

AboutRuby
fonte
2
Observe que a melhor maneira de fazer isso com segurança é atribuir a saída de Symbol.all_symbolsa uma variável e, em seguida, testar a inclusão. Os símbolos são mais rápidos na comparação e você evita converter milhares de símbolos em strings.
coreyward
4
Isso ainda tem o problema de criar o símbolo que não pode ser destruído. Quaisquer testes futuros para esse símbolo serão arruinados. Mas este é apenas um Koan, não precisa fazer muito sentido ou ser rápido, apenas demonstrar como funcionam os símbolos.
AboutRuby
2
Essa resposta não funciona para mim. Se estivermos procurando a existência de um símbolo, por que estamos especificando um argumento string para? include?Se especificássemos :test_method_names_become_symbols, não teríamos que converter todos esses símbolos em strings.
Isaac Rabinovitch
3
Isaac, por causa do problema identificado, que é aquele especificando :test_method_names_become_symbolsna comparação vai criá-lo, então a comparação sempre será verdadeira. Ao converter all_symbolsem strings e comparar strings, podemos distinguir se o símbolo existia antes da comparação.
Stephen
3
Tanto eu não entendo. Se eu fizer >> array_of_symbols = Symbol.all_symbols, então >> array_of_symbols.include? (: Not_yet_used), obtenho falso, e obtenho verdadeiro para algo que foi definido, então não entendo por que a conversão para strings é necessária .
codenoob 01 de
75

Porque se você fizer:

assert_equal true, all_symbols.include?(:test_method_names_become_symbols)

Pode (dependendo de sua implementação de ruby) ser verdadeiro automaticamente, porque :test_method_names_become_symbolscria o símbolo. Veja este relatório de bug .

Andrew Grimm
fonte
1
Portanto, a resposta aceita é a errada (ou assim me parece).
Isaac Rabinovitch
3
Isaac, a outra resposta não está errada, mas não é explicada de forma concisa. com certeza. De qualquer forma, você pode verificar o que Andrew está dizendo com o seguinte: assert_equal true, Symbol.all_symbols.include? (: Abcdef) Isso sempre passará (pelo menos, para mim) independentemente do símbolo. Acho que uma lição é: não tente usar a presença / ausência de símbolos como sinalizador booleano.
Stephen
1
Olhando para esta questão, que ainda me interessa, 2 anos depois, agradeço muito esta resposta e os comentários. Acho que Isaac está certo, esta é a resposta que explica mais claramente porque a conversão para strings pode ser o caminho a percorrer, embora eu ache que você poderia colocar a etapa intermediária (armazenar todos os símbolos antes da comparação) para fazê-la funcionar melhor.
codenoob
4

As duas respostas acima estão corretas, mas à luz da pergunta de Karthik acima, pensei em postar um teste que ilustrasse como alguém pode passar com precisão um símbolo para o includemétodo

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

Uma observação adicional sobre os Koans: faça uso de putsinstruções, bem como testes personalizados, se você não entender nada. Por exemplo, se você vir:

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

e não tenho ideia do que wordspode ser, adicione a linha

puts words

e corra rake na linha de comando. Da mesma forma, testes como o que adicionei acima podem ser úteis em termos de compreensão de algumas das nuances do Ruby.

aceofbassgreg
fonte
Olhando para esta questão, que ainda me interessa, 2 anos depois, agradeço muito esta resposta e os comentários.
codenoob