Perché il creatore di Ruby ha scelto di utilizzare il concetto di simboli?


15

tl; dr: ci sarebbe una definizione agnostica dei simboli e un motivo per averli in altre lingue?

Quindi, perché il creatore di Ruby ha usato il concetto di symbolsnella lingua?

Lo chiedo dal punto di vista di un programmatore non rubino. Ho imparato molte altre lingue e non ho trovato in nessuna di esse la necessità di specificare se avevo a che fare con ciò che Ruby chiama symbols.

La domanda principale è: esiste il concetto di symbolsin Ruby per la performance o solo qualcosa che è necessario a causa del modo in cui è scritta la lingua?

Un programma in Ruby sarebbe più leggero e / o più veloce della sua, diciamo, controparte Python o Javascript? Se è così, sarebbe a causa di symbols?

Dal momento che uno degli intenti di Ruby è di essere facile da leggere e scrivere per gli umani, i suoi creatori non potrebbero facilitare il processo di codifica implementando quei miglioramenti nell'interprete stesso (come potrebbe essere in altre lingue)?

Sembra che tutti vogliono sapere solo cosa symbolssono e come usarli, e non perché sono lì in primo luogo.


Scala ha i simboli, in cima alla mia testa. Penso che molti Lisp lo facciano.
D. Ben Knoble,

Risposte:


17

Il creatore di Ruby, Yukihiro "Matz" Matsumoto, ha pubblicato una spiegazione su come Ruby è stato influenzato da Lisp, Smalltalk, Perl (e Wikipedia dice anche Ada ed Eiffel):

Ruby è un linguaggio progettato nei seguenti passaggi:

  • prendi un linguaggio lisp semplice (come uno precedente a CL).
  • rimuovere le macro, espressione di s.
  • aggiungi un semplice sistema di oggetti (molto più semplice di CLOS).
  • aggiungere blocchi, ispirati a funzioni di ordine superiore.
  • aggiungi metodi trovati in Smalltalk.
  • aggiungi funzionalità trovate in Perl (in modo OO).

Quindi, in teoria Ruby era un Lisp.

Chiamiamolo MatzLisp da ora in poi. ;-)

In qualsiasi compilatore, gestirai identificatori per funzioni, variabili, blocchi con nome, tipi e così via. In genere li memorizzi nel compilatore e li dimentichi nell'eseguibile prodotto, tranne quando aggiungi informazioni di debug.

In Lisp, tali simboli sono risorse di prima classe, ospitate in diversi pacchetti, il che significa che è possibile aggiungere nuovi simboli in fase di esecuzione, collegarli a diversi tipi di oggetti. Ciò è utile durante la meta-programmazione perché si può essere certi di non avere conflitti di denominazione con altre parti del codice.

Inoltre, i simboli vengono internati al momento della lettura e possono essere confrontati per identità, che è un modo efficace per avere nuovi tipi di valori (come numeri, ma astratti). Questo aiuta a scrivere codice in cui usi direttamente valori simbolici, invece di definire i tuoi tipi di enum supportati da numeri interi. Inoltre, ogni simbolo può contenere dati aggiuntivi. Ecco come, ad esempio, Emacs / Slime può allegare metadati da Emacs direttamente nell'elenco delle proprietà di un simbolo.

La nozione di simbolo è centrale in Lisp. Dai un'occhiata ad esempio a PAIP (Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp, Norvig) per esempi dettagliati.


5
Buona risposta. Tuttavia non sono d'accordo con Matz: non avrei mai pensato di chiamare una lingua senza macro un dialetto lisp. Le strutture di lisp di runtime-metaprogrammazione sono precisamente la cosa che dà a questa lingua il suo straordinario potere, compensando la sua grammatica abissamente semplicistica, inespressiva.
cmaster - reintegrare monica il

11

Quindi, perché i creatori di Ruby hanno dovuto usare il concetto di symbolsnella lingua?

Bene, non hanno "dovuto" rigorosamente, hanno scelto di. Inoltre, si noti che i termini rigorosamente Symbolnon fanno parte della lingua, fanno parte della libreria principale. Essi non hanno la sintassi letterale-livello di linguaggio, ma avrebbe funzionato altrettanto bene se si doveva costruirli chiamando Symbol::new.

Chiedo dal punto di vista di un programmatore non rubino che cerca di capirlo. Ho imparato molte altre lingue e in nessuna di esse ho trovato la necessità di specificare se avevo a che fare con ciò che Ruby chiama symbols.

Non hai detto cosa sono quelle "molte altre lingue", ma ecco solo un piccolo estratto di lingue che hanno un Symboltipo di dati come Ruby:

Esistono anche altre lingue che forniscono le caratteristiche di Symbols in una forma diversa. In Java, ad esempio, le caratteristiche di Ruby Stringsono divise in due (in realtà tre) tipi: Stringe StringBuilder/ StringBuffer. D'altra parte, le caratteristiche del Symboltipo di Ruby sono piegate nel Stringtipo Java : Java Strings può essere internato , stringhe letterali e Strings che sono il risultato di espressioni costanti valutate in fase di compilazione vengono automaticamente internate, le Strings generate dinamicamente possono essere internate chiamando il String.internmetodo. Un internato Stringin Java è esattamente come Symbolin Ruby, ma non è implementato come un tipo separato, è solo uno stato diverso rispetto a un JavaStringpuò essere in. (Nota: nelle versioni precedenti di Ruby, String#to_symveniva chiamato String#interne quel metodo esiste ancora oggi come alias legacy.)

La domanda principale potrebbe essere: il concetto di symbolsin Ruby esiste come intento performativo su se stesso e su altre lingue,

Symbols sono innanzitutto un tipo di dati con semantica specifica . Queste semantiche consentono anche di implementare alcune operazioni performanti (ad es. Test rapido sull'uguaglianza O (1)), ma non è questo lo scopo principale.

o semplicemente qualcosa che è necessario per esistere a causa del modo in cui è scritta la lingua?

Symbols non sono affatto necessari nella lingua Ruby, Ruby funzionerebbe perfettamente senza di loro. Sono puramente una funzione di libreria. Esiste esattamente un posto nel linguaggio che è legato a Symbols: defun'espressione di definizione del metodo Symbolrestituisce una denotazione del nome del metodo che viene definito. Tuttavia, si tratta di una modifica piuttosto recente, prima che il valore restituito fosse semplicemente lasciato non specificato. La risonanza magnetica è stata semplicemente valutata nil, Rubinio ha valutato un Rubinius::CompiledMethodoggetto e così via. Sarebbe anche possibile valutare un UnboundMethod... o solo un String.

Un programma in Ruby sarebbe più leggero e / o più veloce della sua, diciamo, controparte Python o Node? Se è così, sarebbe a causa di symbols?

Non sono sicuro di quello che stai chiedendo qui. Le prestazioni dipendono principalmente dalla qualità dell'implementazione, non dal linguaggio. Inoltre, Node non è nemmeno una lingua, è un framework I / O per ECMAScript. Eseguendo uno script equivalente su IronPython e MRI, è probabile che IronPython sia più veloce. Eseguendo uno script equivalente su CPython e JRuby + Truffle, è probabile che JRuby + Truffle sia più veloce. Questo non ha nulla a che fare con Symbols ma con la qualità dell'implementazione: JRuby + Truffle ha un compilatore che ottimizza in modo aggressivo, oltre a tutta la macchina di ottimizzazione di una JVM ad alte prestazioni, CPython è un semplice interprete.

Poiché uno degli intenti di Ruby è di essere facile da leggere e scrivere per gli umani, i suoi creatori non potrebbero facilitare il processo di codifica implementando quei miglioramenti nell'interprete stesso (come potrebbe essere in altre lingue)?

No. Symbols non sono un'ottimizzazione del compilatore. Sono un tipo di dati separato con semantica specifica. Non sono come i flonum di YARV , che sono un'ottimizzazione interna privata per Floats. La situazione non è la stessa di Integer, Bignume Fixnum, che dovrebbe essere un dettaglio di ottimizzazione interno privato invisibile, ma sfortunatamente non lo è. (Questo è finalmente sta per essere fissato in Ruby 2.4, che rimuove Fixnume Bignumfoglie e solo Integer.)

Farlo nel modo in cui lo fa Java, in quanto uno stato speciale di normali Stringsignifica che devi sempre stare attento al fatto che i tuoi siano o meno Stringin quello stato speciale e in quali circostanze si trovino automaticamente in quello stato speciale e quando no. Questo è un onere molto più elevato rispetto al semplice avere un tipo di dati separato.

Ci sarebbe una definizione agnostica dei simboli e un motivo per averli in altre lingue?

Symbolè un tipo di dati che indica il concetto di nome o etichetta . Symbolsono oggetti di valore , immutabili, di solito immediati (se il linguaggio distingue una cosa del genere), apolidi e non hanno identità. Due Symbols che sono uguali sono ugualmente garantiti, in altre parole, due Symbols che sono uguali sono effettivamente gli stessi Symbol. Ciò significa che l'uguaglianza di valore e l'uguaglianza di riferimento sono la stessa cosa, e quindi l'uguaglianza è efficiente e O (1).

I motivi per averli in una lingua sono davvero gli stessi, indipendentemente dalla lingua. Alcune lingue dipendono più da esse di altre.

Nella famiglia Lisp, ad esempio, non esiste un concetto di "variabile". Invece, hai Symbols associato ai valori.

In lingue con capacità riflettenti o introspettive, Symbols sono spesso utilizzati per indicare i nomi delle entità riflesse nelle API di riflessione, ad esempio in Ruby, Object#methods, Object#singleton_methods, Object#public_methods, Object#protected_methods, e Object#public_methodsrestituire un Arraydi Symbols (anche se potrebbero altrettanto bene restituire un Arraydi Methods). Object#public_sendprende un Symboldenotando il nome del messaggio da inviare come argomento (anche se accetta anche un String, Symbolè più semanticamente corretto).

In ECMAScript, gli Symbols sono un elemento fondamentale per rendere ECMAScript sicuro in futuro. Inoltre svolgono un ruolo importante nella riflessione.


Gli atomi di Erlang furono prelevati direttamente da Prolog (Robert Virding me lo disse ad un certo punto)
Zachary K

2

I simboli sono utili in Ruby e li vedrai in tutto il codice Ruby perché ogni simbolo viene riutilizzato ogni volta che viene referenziato. Questo è un miglioramento delle prestazioni rispetto alle stringhe perché ogni utilizzo di una stringa non salvata in una variabile crea un nuovo oggetto in memoria. Ad esempio, se uso più volte la stessa stringa di una chiave hash:

my_hash = {"a" => 1, "b" => 2, "c" => 3}
100_000.times { |i| puts my_hash["a"] }

La stringa "a" viene creata 101.000 volte in memoria. Se invece ho usato un simbolo:

my_hash = {a: 1, b: 2, c: 3}
100_000.times { |i| puts my_hash[:a] }

Il simbolo :aè ancora un oggetto in memoria. Questo rende i simboli molto più efficienti delle stringhe.

AGGIORNAMENTO Ecco un benchmark (tratto da Codecademy ) che dimostra la differenza di prestazioni:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  100_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  100_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

Ecco i miei risultati per il mio MBP:

String time: 0.1254125550040044 seconds.
Symbol time: 0.07360960397636518 seconds.

C'è una chiara differenza nell'uso di stringhe e simboli per identificare semplicemente le chiavi in ​​un hash.


Non sono sicuro che sia così. Mi aspetto che un'implementazione di Ruby esegua lo stesso codice più volte, non analizzando il codice più volte per ogni iterazione. Anche se ogni occorrenza lessicale di "a"è effettivamente una nuova stringa, penso che nel tuo esempio ce ne saranno esattamente due "a"(e un'implementazione potrebbe persino condividere la memoria fino a quando una di esse non è mutata). Per creare milioni di stringhe, probabilmente dovresti usare String.new ("a"). Ma non sono esperto di Ruby, quindi forse mi sbaglio.
coredump,

1
In una delle lezioni di Codecademy, generano un punto di riferimento per stringhe contro simboli, proprio come il mio esempio. Lo aggiungerò alla risposta.
Keith Mattix,

1
Grazie per aver aggiunto il benchmark. Il test mostra il guadagno atteso ottenuto utilizzando i simboli anziché le stringhe, a causa di un test più veloce nella tabella hash (confronto tra identità e stringa), ma non è possibile dedurre che le stringhe vengano allocate ad ogni iterazione. Ho aggiunto una versione con string_AZ[String.new("r")]per vedere se questo fa la differenza. Ricevo 21ms per stringhe (versione originale), 7ms con simboli e 50ms con stringhe fresche ogni volta. Quindi direi che le stringhe non sono allocate tanto con la "r"versione letterale .
coredump,

1
Ah, quindi ho scavato un po 'di più e in Ruby 2.1 le stringhe sono in effetti condivise. Apparentemente mi mancava quell'aggiornamento; Grazie per la segnalazione. Tornando alla domanda originale, penso che entrambi i benchmark mostrino l'utilità dei simboli rispetto alle stringhe.
Keith Mattix il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.