Jak rozumieć symbole w Rubim


85

Pomimo przeczytania „ Zrozumienia symboli rubinowych ”, nadal jestem zdezorientowany reprezentacją danych w pamięci, jeśli chodzi o używanie symboli. Jeśli symbol, dwa z nich zawarte w różnych obiektach, istnieją w tym samym miejscu pamięci, to jak to się dzieje, że zawierają różne wartości? Spodziewałbym się, że ta sama lokalizacja pamięci będzie zawierać tę samą wartość.

To cytat z linku:

W przeciwieństwie do łańcuchów, symbole o tej samej nazwie są inicjowane i istnieją w pamięci tylko raz podczas sesji programu ruby

Nie rozumiem, jak udaje mu się rozróżniać wartości zawarte w tej samej lokalizacji pamięci.

Rozważmy ten przykład:

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1i patient2oba są hashami, w porządku. :rubyjednak jest symbolem. Gdybyśmy mieli wyprowadzić następujące dane:

patient1.each_key {|key| puts key.to_s}

Co wtedy zostanie wydrukowane? "red"lub "programming"?

Zapominając na chwilę o hashach, myślę, że symbol jest wskaźnikiem do wartości. Mam następujące pytania:

  • Czy mogę przypisać wartość do symbolu?
  • Czy symbol jest tylko wskaźnikiem do zmiennej zawierającej wartość?
  • Jeśli symbole są globalne, czy oznacza to, że symbol zawsze wskazuje na jedną rzecz?

1
Wyświetli ": ruby", ponieważ drukujesz symbol. Jeśli powiesz puts patient1[:ruby], wydrukuje "czerwony", jeśli powiesz puts patient2[:ruby], wypisze "programowanie".
ROSS

1
Symbol NIE jest wskaźnikiem do wartości. Wewnętrznie symbol jest po prostu liczbą całkowitą.
akuhn

Odpowiedzi:


62

Rozważ to:

x = :sym
y = :sym
(x.__id__ == y.__id__ ) && ( :sym.__id__ == x.__id__) # => true

x = "string"
y = "string"
(x.__id__ == y.__id__ ) || ( "string".__id__ == x.__id__) # => false

Tak więc, jakkolwiek tworzysz obiekt symboliczny, o ile jego zawartość jest taka sama, będzie odnosił się do tego samego obiektu w pamięci. Nie stanowi to problemu, ponieważ symbol jest niezmiennym przedmiotem . Ciągi znaków są zmienne.


(W odpowiedzi na komentarz poniżej)

W oryginalnym artykule wartość nie jest przechowywana w symbolu, ale w hashu. Rozważ to:

hash1 = { "string" => "value"}
hash2 = { "string" => "value"}

Tworzy to sześć obiektów w pamięci - cztery obiekty typu string i dwa obiekty hash.

hash1 = { :symbol => "value"}
hash2 = { :symbol => "value"}

To tworzy tylko pięć obiektów w pamięci - jeden symbol, dwa ciągi znaków i dwa obiekty z krzyżykiem.


Przykład w odsyłaczu pokazuje jednak symbole zawierające różne wartości, ale symbol ma tę samą nazwę i to samo miejsce w pamięci. Kiedy są drukowane, mają różne wartości, to jest część, której nie rozumiem. Z pewnością powinny mieć tę samą wartość?
Kezzer

1
Właśnie dokonałem edycji, aby spróbować wyjaśnić, jak nadal jestem zdezorientowany. Mój mózg nie potrafi obliczyć;)
Kezzer

48
Symbole nie zawierają wartości, wartościami. Hashe zawierają wartości.
Mladen Jablanović

5
To Hash(utworzony przez {... => ...} w twoim kodzie) przechowuje pary klucz / wartość, a nie Symbolsame s. W Symbols (np :symbollub :symlub :ruby) są klucze w parach. Tylko jako część Hash„wskazują” na cokolwiek.
James A. Rosen

1
Symbol jest używany jako klucz w haszu, a nie wartość, dlatego mogą być różne, jest to podobne do używania słowa klucz1 = 'ruby' i hash1 = {klucz1 => 'wartość' ...} hash2 = { klucz1 => 'wartość2' ...}.
Joshua Olson,

53

Byłem w stanie naśladować symbole, kiedy o tym myślałem. Łańcuch Ruby to obiekt, który ma kilka metod i właściwości. Ludzie lubią używać łańcuchów jako kluczy, a kiedy łańcuch jest używany jako klucz, wszystkie te dodatkowe metody nie są używane. Dlatego stworzyli symbole, które są obiektami łańcuchowymi z usuniętymi wszystkimi funkcjami, z wyjątkiem tych, które są potrzebne, aby był to dobry klucz.

Pomyśl o symbolach jako o stałych ciągach.


2
Czytając posty, ten prawdopodobnie ma dla mnie największy sens. : Ruby jest po prostu zapisany gdzieś w pamięci, jeśli użyję gdzieś "ruby", a potem znowu gdzieś "ruby", to po prostu powielenie. Zatem używanie symboli jest sposobem na ograniczenie powielania wspólnych danych. Jak mówisz, ciągłe ciągi. Sądzę, że istnieje jakiś podstawowy mechanizm, który sprawi, że ten symbol zostanie ponownie użyty?
Kezzer

@Kezzer Ta odpowiedź jest naprawdę dobra i wydaje mi się słuszna, ale twój komentarz mówi coś innego i jest błędny lub mylący, twój komentarz mówi o powielaniu danych za pomocą ciągów i że to jest powód symboli, to jest złe lub mylące. symbol wielokrotnie nie zużyje więcej miejsca w pamięci, ale możesz go mieć również dla łańcuchów w wielu językach np. w niektórych językach programowania jeśli napiszesz "abc" a gdzie indziej "abc" kompilator zobaczy to ten sam łańcuch wartości i zapisze go w tym samym miejscu, czyniąc go tym samym obiektem, który nazywa się interningiem ciągów, a c # robi to.
barlop

Więc jest to w zasadzie niewiarygodnie lekka wersja struny?
stevec

34

Symbol :rubynie zawiera "red"ani "programming". Symbol :rubyjest tylko symbolem :ruby. To twoje skróty patient1ipatient2 każdy zawiera te wartości, w każdym przypadku wskazywane przez ten sam klucz.

Pomyśl o tym w ten sposób: jeśli wejdziesz do salonu w bożonarodzeniowy poranek i zobaczysz dwa pudełka z etykietą z napisem „Kezzer”. Jeden ma w sobie skarpetki, a drugi węgiel. Nie będziesz się mylić i nie zapytasz, jak „Kezzer” może zawierać zarówno skarpety, jak i węgiel, mimo że to ta sama nazwa. Ponieważ nazwa nie zawiera (gównianych) prezentów. Po prostu na nich wskazuje. Podobnie :rubynie zawiera wartości z twojego skrótu, po prostu je wskazuje.


2
Ta odpowiedź ma sens.
Vass

To brzmi jak całkowita mieszanka skrótów i symboli. Symbol nie wskazuje na wartość, jeśli chcesz powiedzieć, że tak jest w haszu, cóż, może to być dyskusyjne, ale symbol nie musi znajdować się w haszu. Można powiedzieć, mystring = :steveT że symbol niczego nie wskazuje. Klucz w skrócie ma skojarzoną wartość, a kluczem może być symbol. Ale symbol nie musi znajdować się w haszu.
barlop

27

Możesz przypuszczać, że deklaracja, którą złożyłeś, definiuje wartość symbolu jako coś innego niż to, czym jest. W rzeczywistości Symbol jest po prostu „zinternalizowaną” wartością ciągu, która pozostaje stała. To dlatego, że są one przechowywane przy użyciu prostego identyfikatora liczby całkowitej, są często używane, ponieważ jest to bardziej wydajne niż zarządzanie dużą liczbą ciągów o zmiennej długości.

Weźmy przykład:

patient1 = { :ruby => "red" }

Należy to rozumieć jako: „zadeklaruj zmienną pacjenta1 i zdefiniuj ją jako hash, aw tym magazynie wartość 'red' pod kluczem (symbol 'ruby')"

Innym sposobem zapisania tego jest:

patient1 = Hash.new
patient1[:ruby] = 'red'

puts patient1[:ruby]
# 'red'

Kiedy wykonujesz zadanie, nie jest zaskakujące, że wynik, który otrzymujesz, jest identyczny z tym, z którym go przypisałeś.

Koncepcja Symbol może być nieco myląca, ponieważ nie jest cechą większości innych języków.

Każdy obiekt String jest inny, nawet jeśli wartości są identyczne:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148099960
# "foo" 2148099940
# "foo" 2148099920
# "bar" 2148099900
# "bar" 2148099880
# "bar" 2148099860

Każdy symbol o tej samej wartości odnosi się do tego samego obiektu:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

Konwersja ciągów znaków na symbole odwzorowuje identyczne wartości na ten sam unikalny symbol:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  v = v.to_sym
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

Podobnie, konwersja z Symbol na String tworzy za każdym razem odrębny ciąg:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  v = v.to_s
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148097820
# "foo" 2148097700
# "foo" 2148097580
# "bar" 2148097460
# "bar" 2148097340
# "bar" 2148097220

Możesz myśleć o wartościach Symbol jako o wartościach rysowanych z wewnętrznej tabeli Hash i możesz zobaczyć wszystkie wartości, które zostały zakodowane do Symbols za pomocą prostego wywołania metody:

Symbol.all_values

# => [:RUBY_PATCHLEVEL, :vi_editing_mode, :Separator, :TkLSHFT, :one?, :setuid?, :auto_indent_mode, :setregid, :back, :Fail, :RET, :member?, :TkOp, :AP_NAME, :readbyte, :suspend_context, :oct, :store, :WNOHANG, :@seek, :autoload, :rest, :IN_INPUT, :close_read, :type, :filename_quote_characters=, ...

W miarę definiowania nowych symboli za pomocą notacji dwukropków lub przy użyciu .to_sym ta tabela będzie się powiększać.


17

Symbole nie są wskazówkami. Nie zawierają wartości. Symbole po prostu . :rubyjest symbolem :rubyi to wszystko. Nie zawiera wartość, to nie robić , istnieje po prostu jako symbol :ruby. Symbol :rubyjest wartością, tak jak liczba 1. Nie wskazuje innej wartości bardziej niż liczba 1.


13
patient1.each_key {|key| puts key.to_s}

Co wtedy zostanie wydrukowane? „czerwony” czy „programujący”?

Ani też, nie wyświetli "ruby".

Mylisz symbole i skróty. Nie są spokrewnione, ale są przydatne razem. Symbol, o którym mowa, to :ruby; nie ma to nic wspólnego z wartościami w hashu, a jej wewnętrzna reprezentacja w postaci liczb całkowitych będzie zawsze taka sama, a jej „wartość” (po przekonwertowaniu na łańcuch) zawsze będzie miała wartość „ruby”.


10

W skrócie

Symbole rozwiązują problem tworzenia czytelnych dla człowieka, niezmiennych reprezentacji, które mają również tę zaletę, że są prostsze w wyszukiwaniu przez środowisko wykonawcze niż ciągi. Pomyśl o tym jak o nazwie lub etykiecie, których można użyć ponownie.

Dlaczego: czerwony jest lepszy niż „czerwony”

W dynamicznych językach obiektowych tworzysz złożone, zagnieżdżone struktury danych z czytelnymi odniesieniami. Hash jest częstym przypadkiem użycia gdzie odwzorować wartości unikatowych kluczy - unikalny, przynajmniej do każdej instancji. Nie możesz mieć więcej niż jednego „czerwonego” klucza na hash.

Jednak bardziej wydajnym procesorem byłoby użycie indeksu liczbowego zamiast kluczy łańcuchowych. Dlatego symbole zostały wprowadzone jako kompromis między szybkością a czytelnością. Symbole są znacznie łatwiejsze do rozpoznania niż ich odpowiednik. Będąc czytelnym dla człowieka i łatwym do rozpoznawania przez środowisko wykonawcze, symbole są idealnym dodatkiem do dynamicznego języka.

Korzyści

Ponieważ symbole są niezmienne, mogą być współużytkowane w czasie wykonywania. Jeśli dwie instancje skrótu mają wspólne leksykograficzne lub semantyczne zapotrzebowanie na czerwony element, symbol: red zużywałby mniej więcej połowę pamięci, której ciąg „red” wymagałby dla dwóch haszów.

Ponieważ: czerwony zawsze powraca do tej samej lokalizacji w pamięci, może być ponownie użyty w stu wystąpieniach skrótu prawie bez zwiększania pamięci, podczas gdy użycie „czerwonego” spowoduje dodanie kosztu pamięci, ponieważ każda instancja skrótu musiałaby przechowywać zmienny ciąg znaków kreacja.

Nie jestem pewien, jak Ruby faktycznie implementuje symbole / ciągi znaków, ale wyraźnie symbol oferuje mniejszy narzut implementacji w środowisku wykonawczym, ponieważ jest stałą reprezentacją. Symbole plus wymagają o jeden znak mniej niż ciąg cytowany w cudzysłowie, a mniej pisania to wieczna pogoń prawdziwych Rubyistów.

Podsumowanie

Z symbolem takim jak: red uzyskujesz czytelność reprezentacji ciągu z mniejszym narzutem ze względu na koszt operacji porównywania ciągów i konieczność przechowywania każdej instancji ciągu w pamięci.


4

Polecam przeczytanie artykułu w Wikipedii o tablicach z haszowaniem - myślę, że pomoże ci to zrozumieć, co {:ruby => "red"}tak naprawdę oznacza.

Kolejne ćwiczenie, które może pomóc ci zrozumieć sytuację: rozważ {1 => "red"}. Semantycznie nie oznacza to „ustaw wartość 1na "red"”, co jest niemożliwe w Rubim. Oznacza raczej „utwórz obiekt Hash i zapisz wartość "red"klucza 1.


3
patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1i patient2oba są hashami, w porządku. :rubyjednak jest symbolem. Gdybyśmy mieli wyprowadzić następujące dane:

patient1.each_key {|key| puts key.to_s}

Co wtedy zostanie wydrukowane? „czerwony” czy „programujący”?

Ani też, oczywiście. Dane wyjściowe będąruby . A tak przy okazji, mogłeś się dowiedzieć w krótszym czasie, niż zajęło ci wpisanie pytania, po prostu wpisując je w IRB.

Dlaczego miałoby to być redlub programming? Symbole zawsze oceniają się same. Wartość symbolu :rubyto :rubysam symbol , a ciąg znaków reprezentujący symbol :rubyto wartość ciągu "ruby".

[BTW: w putskażdym razie zawsze konwertuje swoje argumenty na łańcuchy. Nie ma potrzeby do tego dzwonić to_s.]


Nie mam IRBa na obecnym komputerze, więc nie byłbym w stanie go zainstalować, więc dlaczego, więc przepraszam za to.
Kezzer

2
@Kezzer: Nie martw się, byłem po prostu ciekawy. Czasami pogrążasz się tak głęboko w problemie, że nie widzisz już nawet najprostszych rzeczy. Kiedy w zasadzie wyciąłem i wkleiłem twoje pytanie do IRB, po prostu zastanawiałem się: „dlaczego on tego nie zrobił?” I nie martw się, nie jesteś pierwszą (ani ostatnią), która pyta „co robi ten wydruk”, gdy odpowiedź brzmi „po prostu uruchom to!” BTW: oto Twój natychmiastowy IRB, zawsze i wszędzie, bez konieczności instalacji: TryRuby.Org lub Ruby-Versions.Net zapewnia dostęp SSH do wszystkich wydanych kiedykolwiek wersji MRI + YARV + JRuby + Rubinius + REE.
Jörg W Mittag

Dzięki, teraz się tym bawię. Nadal jestem trochę zdezorientowany, więc wracam do tego ponownie.
Kezzer

0

Jestem nowy w Ruby, ale myślę (mam nadzieję?), Że jest to prosty sposób na to ...

Symbol nie jest zmienną ani stałą. Nie zastępuje wartości ani nie wskazuje na nią. Symbol JEST wartością.

Wszystko to jest łańcuchem bez narzutu obiektu. Tekst i tylko tekst.

Więc to:

"hellobuddy"

Jest taki sam jak ten:

:hellobuddy

Z wyjątkiem tego, że nie możesz zrobić, na przykład: hellobuddy.upcase. Jest to wartość ciągu i TYLKO wartość ciągu.

Podobnie to:

greeting =>"hellobuddy"

Jest taki sam jak ten:

greeting => :hellobuddy

Ale znowu bez narzutu obiektu ciągu.


-1

Jednym z łatwych sposobów, by to owinąć, jest myślenie: „co by było, gdybym użył sznurka zamiast symbolu?

patient1 = { "ruby" => "red" }
patient2 = { "ruby" => "programming" }

To wcale nie jest mylące, prawda? Używasz „ruby” jako klucza w skrócie .

"ruby"jest literałem łańcuchowym, więc to jest wartość. Adres pamięci lub wskaźnik nie są dostępne. Za każdym razem, gdy wywołujesz "ruby", tworzysz nowe jego wystąpienie, to znaczy tworzysz nową komórkę pamięci zawierającą tę samą wartość - "ruby".

Hash następnie brzmi „jaka jest moja wartość klucza? Och, to jest "ruby". Następnie mapuje tę wartość na„ czerwony ”lub„ programowanie ”. Innymi słowy, :rubynie odwołuje się do "red"lub "programming". Hash jest mapowany :ruby na "red"lub "programming".

Porównaj to z sytuacją, gdy używamy symboli

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

Wartość :rubyjest również "ruby"skuteczna.

Czemu? Ponieważ symbole są zasadniczo stałymi łańcuchami . Stałe nie mają wielu instancji. To ten sam adres pamięci. A adres pamięci ma określoną wartość po wyłuskowaniu. W przypadku symboli nazwa wskaźnika jest symbolem, a wyłuskaną wartością jest ciąg, który pasuje do nazwy symbolu, w tym przypadku "ruby".

W haszu nie używasz symbolu, wskaźnika, ale odroczoną wartość. Nie używasz :ruby, ale "ruby". Następnie hash wyszukuje klucz "ruby", wartość to "red"lub "programming", w zależności od tego, jak zdefiniowałeś skrót.

Koncepcja zmiany paradygmatu i zabierania do domu polega na tym, że wartość symbolu jest pojęciem całkowicie odrębnym od wartości odwzorowanej przez hash, biorąc pod uwagę klucz tego skrótu.


Jaki jest błąd lub błąd w tym wyjaśnieniu, ci, którzy je odrzucają? ciekawy ze względu na naukę.
ahnbizcad

tylko dlatego, że analogia może być dla niektórych niesmaczna, nie oznacza, że ​​jest wadliwa.
ahnbizcad

2
Niekoniecznie widzę żadnych błędów, ale niezwykle trudno jest określić, co próbujesz powiedzieć w tej odpowiedzi.
Reverse Engineered

x jest interpretowany i konceptualizowany w różny sposób w oparciu o kontekst / podmiot dokonujący interpretacji. dość proste.
ahnbizcad

zmienił odpowiedź. Dzięki za opinie.
ahnbizcad
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.