tl; dr: Czy istniałaby zależna od języka definicja symboli i powód, aby mieć je w innych językach?
Dlaczego więc twórca języka Ruby zastosował tę koncepcję symbols
w języku?
Pytam o to z perspektywy programisty nie-rubinowego. Nauczyłem się wielu innych języków i w żadnym z nich nie znalazłem potrzeby określania, czy mam do czynienia z tym, co nazywa Ruby symbols
.
Główne pytanie brzmi: czy symbols
w Ruby istnieje pojęcie wydajności, czy tylko coś, co jest potrzebne ze względu na sposób pisania języka?
Czy program w Rubim byłby lżejszy i / lub szybszy niż jego, powiedzmy, odpowiednik w Pythonie lub JavaScript? Jeśli tak, czy to z tego powodu symbols
?
Skoro jednym z zamierzeń Ruby jest łatwość czytania i pisania dla ludzi, czy twórcy nie mogliby ułatwić procesu kodowania poprzez wdrożenie tych ulepszeń w samym tłumaczu (tak jak w innych językach)?
Wygląda na to, że każdy chce wiedzieć tylko, co to symbols
jest i jak z nich korzystać, a nie dlaczego.
źródło
Odpowiedzi:
Twórca Ruby, Yukihiro „Matz” Matsumoto, opublikował wyjaśnienie, w jaki sposób na Ruby wpłynęły Lisp, Smalltalk, Perl (a Wikipedia mówi także Ada i Eiffel):
W dowolnym kompilatorze będziesz zarządzać identyfikatorami funkcji, zmiennych, nazwanych bloków, typów i tak dalej. Zazwyczaj przechowuje się je w kompilatorze, a zapomina się o nich w utworzonym pliku wykonywalnym, z wyjątkiem przypadków dodania informacji o debugowaniu.
W Lisp takie symbole są pierwszorzędnymi zasobami hostowanymi w różnych pakietach, co oznacza, że możesz dodawać nowe symbole w czasie wykonywania, wiązać je z różnego rodzaju obiektami. Jest to przydatne podczas metaprogramowania, ponieważ możesz być pewien, że nie będziesz mieć kolizji nazw z innymi częściami kodu.
Ponadto symbole są internalizowane w czasie odczytu i można je porównać według tożsamości, co jest skutecznym sposobem na uzyskanie nowego rodzaju wartości (takich jak liczby, ale abstrakcyjne). Pomaga to w pisaniu kodu, w którym bezpośrednio używasz wartości symbolicznych, zamiast definiować własne typy wyliczeń poparte liczbami całkowitymi. Ponadto każdy symbol może przechowywać dodatkowe dane. W ten sposób na przykład Emacs / Slime może dołączać metadane z Emacsa bezpośrednio do listy właściwości symbolu.
Pojęcie symbolu jest kluczowe w Lisp. Spójrz na przykład na PAIP (Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp, Norvig), aby uzyskać szczegółowe przykłady.
źródło
Cóż, nie ściśle „musieli”, postanowili. Zauważ też, że ściśle mówiąc,
Symbol
nie są częścią języka, są częścią podstawowej biblioteki. Oni nie mają składnię języka dosłownego poziomu, ale będą działać tak samo dobrze, jeśli trzeba było skonstruować je nazywającSymbol::new
.Nie powiedziałeś, czym są te „wiele innych języków”, ale oto mały fragment języków, które mają
Symbol
typ danych taki jak Ruby:Istnieją również inne języki, które zapewniają funkcje
Symbol
s w innej formie. Na przykład w Javie funkcje RubyString
są podzielone na dwa (właściwie trzy) typy:String
iStringBuilder
/StringBuffer
. Z drugiej strony, funkcje RubySymbol
typu są składane w języku JavaString
Typ: JavaString
s może być internowany , ciągami tekstowymi iString
y, które są wynikiem kompilacji oceniano wyrażenia stałe są automatycznie internowany, dynamicznie generowaneString
s może być internowany przez wywołanieString.intern
metodą. InternowanyString
w Javie jest dokładnie jakSymbol
w Ruby, ale nie jest zaimplementowany jako osobny typ, to po prostu inny stan niż JavaString
może być w. (Uwaga: we wcześniejszych wersjach Ruby,String#to_sym
kiedyś był wywoływany,String#intern
a ta metoda nadal istnieje jako starszy alias).Symbol
s są przede wszystkim typem danych o określonej semantyce . Ta semantyka umożliwia także implementację niektórych wydajnych operacji (np. Szybkie testowanie równości O (1)), ale to nie jest główny cel.Symbol
w języku Ruby wcale nie są potrzebne, bez nich Ruby działałaby dobrze. Są to wyłącznie funkcje biblioteczne. Jest dokładnie jedno miejsce w języku powiązanym zSymbol
s:def
wyrażenie definicji metody zwraca wartośćSymbol
oznaczającą nazwę definiowanej metody. Jest to jednak dość niedawna zmiana, wcześniej wartość zwrotna była po prostu nieokreślona. MRI po prostu ocenił nanil
, Rubinius ocenił naRubinius::CompiledMethod
obiekt i tak dalej. Możliwe byłoby również dokonanie oceny doUnboundMethod
… lub po prostuString
.Nie jestem pewien, o co tu pytasz. Wydajność zależy głównie od jakości wdrożenia, a nie od języka. Ponadto Node nie jest nawet językiem, to zdeklarowana platforma we / wy dla ECMAScript. Uruchomienie równoważnego skryptu na IronPython i MRI, IronPython prawdopodobnie będzie szybszy. Uruchomienie równoważnego skryptu na CPython i JRuby + Trufla, JRuby + Trufla prawdopodobnie będzie szybsze. Nie ma to nic wspólnego z
Symbol
s, ale z jakością implementacji: JRuby + Truffle ma agresywnie optymalizujący kompilator, a także całą maszynę optymalizacyjną o wysokiej wydajności JVM, CPython to prosty interpreter.Nie. Nie
Symbol
są optymalizacjami kompilatora. Są osobnym typem danych o specyficznej semantyce. Nie są jak flonum YARV , które są prywatną wewnętrzną optymalizacją dlaFloat
s. Sytuacja nie jest taka sama, jak w przypadkuInteger
,Bignum
iFixnum
, który powinien być niewidoczny prywatny wewnętrzny szczegół optymalizacja, ale niestety nie jest. (Jest to ostatecznie będzie ustalona w Ruby 2.4, który usuwaFixnum
iBignum
liści i tylkoInteger
).Robiąc to tak, jak robi to Java, ponieważ specjalny stan normalnych
String
s oznacza, że zawsze musisz uważać na to, czy twojeString
są w tym szczególnym stanie, i w jakich okolicznościach są one automatycznie w tym szczególnym stanie, a kiedy nie. To znacznie większe obciążenie niż po prostu posiadanie osobnego typu danych.Symbol
to typ danych, który oznacza pojęcie nazwy lub etykiety .Symbol
s są obiektami wartości , niezmiennymi, zwykle natychmiastowymi (jeśli język je odróżnia), bezpaństwowcami i nie mają tożsamości. DwieSymbol
równe są również zagwarantowane, że są identyczne, innymi słowy, dwieSymbol
równe są w rzeczywistości takie sameSymbol
. Oznacza to, że równość wartości i równość odniesienia są tym samym, a zatem równość jest efektywna i O (1).Powody, dla których mają je w języku, są naprawdę takie same, niezależnie od języka. Niektóre języki polegają na nich bardziej niż inne.
Na przykład w rodzinie Lisp nie ma pojęcia „zmiennej”. Zamiast tego jesteś
Symbol
powiązany z wartościami.W językach z możliwościami odblaskowych lub introspekcji,
Symbol
a często są wykorzystywane do określenia nazwy odbitych podmiotów API refleksji, na przykład w RubyObject#methods
,Object#singleton_methods
,Object#public_methods
,Object#protected_methods
, iObject#public_methods
zwracaćArray
zSymbol
s (choć równie dobrze może zwracaćArray
odMethod
s).Object#public_send
przyjmujeSymbol
nazwę argumentu do wysłania jako argument (chociaż akceptuje równieżString
również,Symbol
jest bardziej semantycznie poprawny).W ECMAScript
Symbol
s to podstawowy element składowy zapewniający bezpieczeństwo ECMAScript w przyszłości. Odgrywają także dużą rolę w refleksji.źródło
Symbole są przydatne w Rubim, a zobaczysz je w całym kodzie Ruby, ponieważ każdy symbol jest ponownie używany za każdym razem, gdy się do niego odwołuje. Jest to poprawa wydajności w porównaniu z łańcuchami, ponieważ każde użycie łańcucha, który nie jest zapisany w zmiennej, tworzy nowy obiekt w pamięci. Na przykład, jeśli użyję tego samego ciągu wiele razy jako klucz skrótu:
Ciąg „a” jest tworzony 101 000 razy w pamięci. Jeśli zamiast tego użyłem symbolu:
Symbol
:a
jest wciąż jednym obiektem w pamięci. To sprawia, że symbole są znacznie bardziej wydajne niż łańcuchy.AKTUALIZACJA Oto punkt odniesienia (wzięty z Codecademy ), który pokazuje różnicę wydajności:
Oto moje wyniki dla mojego MBP:
Istnieje wyraźna różnica w używaniu ciągów vs. symboli do identyfikowania kluczy w skrócie.
źródło
"a"
jest rzeczywiście świeżym ciągiem znaków, myślę, że w twoim przykładzie będą dokładnie dwa"a"
(i implementacja może nawet dzielić pamięć, dopóki jedno z nich nie zostanie zmutowane). Aby utworzyć miliony ciągów, prawdopodobnie będziesz musiał użyć String.new („a”). Ale nie znam się dobrze na Ruby, więc może się mylę.string_AZ[String.new("r")]
, aby zobaczyć, czy to robi różnicę. Dostaję 21 ms dla napisów (wersja oryginalna), 7 ms z symbolami i 50 ms ze świeżymi napisami za każdym razem. Powiedziałbym więc, że ciągi nie są przydzielane tak często w"r"
wersji dosłownej .