Ruby Koans: Po co konwertować listę symboli na łańcuchy

85

Mam na myśli ten test w about_symbols.rb w 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?

Dlaczego dokładnie musimy najpierw przekonwertować tę listę na ciągi?

koderkod
źródło

Odpowiedzi:

110

Ma to związek z działaniem symboli. Dla każdego symbolu istnieje tylko jeden z nich. W tle symbol to po prostu liczba, do której odnosi się nazwa (zaczynająca się od dwukropka). Zatem porównując równość dwóch symboli, porównujesz tożsamość obiektu, a nie zawartość identyfikatora, który odnosi się do tego symbolu.

Jeśli miałbyś wykonać prosty test : test == "test" , będzie fałszywy. Tak więc, jeśli miałbyś zebrać wszystkie symbole zdefiniowane do tej pory w tablicę, musiałbyś najpierw przekonwertować je na łańcuchy przed ich porównaniem. Nie możesz tego zrobić w odwrotny sposób (najpierw przekonwertuj ciąg, który chcesz porównać na symbol), ponieważ spowodowałoby to utworzenie pojedynczej instancji tego symbolu i „zanieczyszczenie” listy symbolem, który testujesz pod kątem istnienia.

Mam nadzieję, że to pomoże. To trochę dziwne, ponieważ musisz przetestować obecność symbolu bez przypadkowego tworzenia tego symbolu podczas testu. Zwykle nie widzisz takiego kodu.

O Ruby
źródło
2
Zauważ, że lepszym sposobem na bezpieczne wykonanie tego jest przypisanie wyniku Symbol.all_symbolszmiennej do zmiennej, a następnie przetestowanie jej włączenia. Symbole są szybsze w porównaniu i unika się konwersji tysięcy symboli na łańcuchy.
coreyward
4
To wciąż ma problem ze stworzeniem symbolu, którego nie można zniszczyć. Wszelkie przyszłe testy tego symbolu zostaną zrujnowane. Ale to tylko Koan, nie musi mieć większego sensu ani być szybki, wystarczy pokazać, jak działają symbole.
About Ruby
2
Ta odpowiedź nie działa dla mnie. Jeśli szukamy symbolu, dlaczego określamy argument będący ciągiem znaków na include?Jeśli :test_method_names_become_symbolsokreśliliśmy, nie musielibyśmy konwertować wszystkich tych symboli na łańcuchy.
Izaak Rabinowicz
3
Izaaka, ze względu na zidentyfikowany problem, który polega na tym, że określenie :test_method_names_become_symbolsw porównaniu spowoduje jego utworzenie, więc porównanie zawsze będzie prawdziwe. Konwertując all_symbolsna łańcuchy i porównując łańcuchy, możemy odróżnić, czy symbol istniał przed porównaniem.
Stephen
3
Tyle nie rozumiem. Jeśli zrobię >> array_of_symbols = Symbol.all_symbols, to >> array_of_symbols.include? (: Not_yet_used), otrzymuję fałsz i otrzymuję prawdę dla czegoś, co zostało zdefiniowane, więc nie rozumiem, dlaczego konwersja na ciągi jest konieczna .
codenoob
75

Bo jeśli to zrobisz:

assert_equal true, all_symbols.include?(:test_method_names_become_symbols)

Może (w zależności od twojej implementacji ruby) automatycznie być prawdziwe, ponieważ :test_method_names_become_symbolstworzy symbol. Zobacz ten raport o błędzie .

Andrew Grimm
źródło
1
Tak więc przyjęta odpowiedź jest błędna (a przynajmniej tak mi się wydaje).
Izaak Rabinowicz
3
Izaaku, druga odpowiedź nie jest błędna, ale nie jest wyjaśniona tak zwięźle. na pewno. W każdym razie możesz zweryfikować to, co mówi Andrew, za pomocą następującego polecenia: assert_equal true, Symbol.all_symbols.include? (: Abcdef) To zawsze przejdzie (przynajmniej w przypadku mnie) niezależnie od symbolu. Myślę, że jedna lekcja brzmi: nie próbuj używać obecności / braku symboli jako flagi logicznej.
Stephen
1
Patrząc na to pytanie, które wciąż mnie interesuje, 2 lata później bardzo doceniam tę odpowiedź i komentarze. Myślę, że Izaak ma rację, to jest odpowiedź, która najwyraźniej wyjaśnia, dlaczego konwersja na łańcuchy może być drogą do zrobienia, chociaż myślę, że możesz umieścić krok pośredni (zapisz wszystkie symbole przed porównaniem), aby działał lepiej.
codenoob
3

Obie powyższe odpowiedzi są poprawne, ale w świetle powyższego pytania Karthika pomyślałem, że opublikuję test, który ilustruje, jak dokładnie można przekazać symbol do includemetody

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

Dodatkowa uwaga na temat Koanów: korzystaj z putsoświadczeń i niestandardowych testów, jeśli nic nie rozumiesz. Na przykład, jeśli widzisz:

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

i nie mam pojęcia, co wordsmoże być, dodaj linię

puts words

i uruchom rakew wierszu poleceń. Podobnie testy, takie jak ten, który dodałem powyżej, mogą być pomocne w zrozumieniu niektórych niuansów Rubiego.

aceofbassgreg
źródło
Patrząc na to pytanie, które wciąż mnie interesuje, 2 lata później naprawdę doceniam tę odpowiedź i komentarze.
codenoob