Pourquoi le créateur de Ruby a-t-il choisi d'utiliser le concept de symboles?


15

tl; dr: Y aurait-il une définition indépendante des langues des symboles et une raison de les avoir dans d'autres langues?

Alors, pourquoi le créateur de Ruby a-t-il utilisé le concept de symbolsdans le langage?

Je pose cette question du point de vue d'un programmeur non rubis. J'ai appris beaucoup d'autres langues, et je n'ai trouvé dans aucune d'entre elles la nécessité de préciser si j'avais affaire ou non à ce que Ruby appelle symbols.

La question principale est la suivante: le concept de symbolsRuby existe-t-il pour la performance, ou simplement quelque chose qui est nécessaire en raison de la façon dont le langage est écrit?

Un programme dans Ruby serait-il plus léger et / ou plus rapide que son équivalent, disons, Python ou Javascript? Si oui, serait-ce à cause de cela symbols?

Étant donné que l'une des intentions de Ruby est d'être facile à lire et à écrire pour les humains, ses créateurs n'ont-ils pas pu faciliter le processus de codage en mettant en œuvre ces améliorations dans l'interpréteur lui-même (comme cela pourrait être dans d'autres langues)?

On dirait que tout le monde veut savoir ce que symbolssont et comment les utiliser, et non pas pourquoi ils sont là en premier lieu.


Scala a des symboles, sur le dessus de ma tête. Je pense que beaucoup de Lisps le font.
D. Ben Knoble

Réponses:


17

Le créateur de Ruby, Yukihiro "Matz" Matsumoto, a posté une explication sur la façon dont Ruby a été influencé par Lisp, Smalltalk, Perl (et Wikipedia dit aussi Ada et Eiffel):

Ruby est un langage conçu dans les étapes suivantes:

  • prendre un langage simple et clair (comme un avant CL).
  • supprimer les macros, s-expression.
  • ajouter un système d'objet simple (beaucoup plus simple que CLOS).
  • ajouter des blocs, inspirés par des fonctions d'ordre supérieur.
  • ajouter des méthodes trouvées dans Smalltalk.
  • ajouter des fonctionnalités trouvées en Perl (de manière OO).

Donc, Ruby était un Lisp à l'origine, en théorie.

Appelons-le désormais MatzLisp. ;-)

Dans tout compilateur, vous allez gérer les identifiants des fonctions, des variables, des blocs nommés, des types, etc. Généralement, vous les stockez dans le compilateur et les oubliez dans l'exécutable produit, sauf lorsque vous ajoutez des informations de débogage.

En Lisp, ces symboles sont des ressources de première classe, hébergées dans différents packages, ce qui signifie que vous pouvez ajouter de nouveaux symboles au moment de l'exécution, les lier à différents types d'objets. Ceci est utile lors de la méta-programmation, car vous pouvez être sûr que vous n'aurez pas de collisions de noms avec d'autres parties du code.

De plus, les symboles sont internés au moment de la lecture et peuvent être comparés par identité, ce qui est un moyen efficace d'avoir de nouveaux types de valeurs (comme des nombres, mais abstraits). Cela aide à écrire du code dans lequel vous utilisez directement des valeurs symboliques, au lieu de définir vos propres types d'énumération soutenus par des entiers. De plus, chaque symbole peut contenir des données supplémentaires. C'est ainsi, par exemple, qu'Emacs / Slime peut attacher des métadonnées d'Emacs directement dans la liste des propriétés d'un symbole.

La notion de symbole est centrale en Lisp. Jetez un œil par exemple à PAIP (Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp, Norvig) pour des exemples détaillés.


5
Bonne réponse. Cependant je ne suis pas d'accord avec Matz: je ne penserais jamais à appeler une langue sans macros un dialecte vif. Les fonctions de métaprogrammation à l'exécution de lisp sont précisément ce qui donne à ce langage sa puissance impressionnante, compensant sa grammaire extrêmement simpliste et inexpressive.
cmaster - réintègre monica le

11

Alors, pourquoi les créateurs de Ruby ont-ils dû utiliser le concept de symbolsdans le langage?

Eh bien, ils n'ont pas strictement «dû», ils ont choisi de le faire. Notez également que les Symbols à proprement parler ne font pas partie du langage, ils font partie de la bibliothèque principale. Ils n'ont une syntaxe littérale du niveau de la langue, mais ils travailleraient aussi bien si vous deviez les construire en appelant .Symbol::new

Je demande du point de vue d'un programmeur non rubis essayant de le comprendre. J'ai appris beaucoup d'autres langues et je n'ai trouvé dans aucune d'entre elles la nécessité de spécifier si j'avais affaire ou non à ce que Ruby appelle symbols.

Vous n'avez pas dit ce que sont ces "beaucoup d'autres langues", mais voici juste un petit extrait de langues qui ont un Symboltype de données comme Ruby:

Il existe également d'autres langages qui fournissent les fonctionnalités de Symbols sous une forme différente. En Java, par exemple, les fonctionnalités de Ruby Stringsont divisées en deux (en fait trois) types: Stringet StringBuilder/ StringBuffer. D'un autre côté, les fonctionnalités du Symboltype Ruby sont repliées dans le Stringtype Java : les Java Strings peuvent être internés , les chaînes littérales et les Strings qui sont le résultat d'expressions constantes évaluées au moment de la compilation sont automatiquement internées, les Strings générés dynamiquement peuvent être internés en appelant la String.internméthode. Un interné Stringen Java est exactement comme un SymbolRuby, mais il n'est pas implémenté comme un type séparé, c'est juste un état différent qu'un JavaStringpeut être dans. (Remarque: dans les versions antérieures de Ruby, elle String#to_symétait appelée String#internet cette méthode existe encore aujourd'hui en tant qu'alias hérité.)

La question principale pourrait être: le concept de symbolsRuby existe-t-il comme une intention de performance sur lui-même et sur d'autres langages,

Symbols sont avant tout un type de données avec une sémantique spécifique . Ces sémantiques permettent également d'implémenter certaines opérations performantes (par exemple le test d'égalité rapide O (1)), mais ce n'est pas le but principal.

ou simplement quelque chose qui doit exister en raison de la façon dont la langue est écrite?

Symbols ne sont pas du tout nécessaires dans le langage Ruby, Ruby fonctionnerait très bien sans eux. Ils sont purement une fonctionnalité de bibliothèque. Il y a exactement un endroit dans le langage qui est lié à Symbols: une defexpression de définition de méthode s'évalue en Symbolindiquant le nom de la méthode en cours de définition. Cependant, c'est un changement assez récent, avant cela, la valeur de retour était simplement laissée non spécifiée. L'IRM simplement évaluée à nil, Rubinius évaluée à un Rubinius::CompiledMethodobjet, et ainsi de suite. Il serait également possible d'évaluer un UnboundMethod… ou juste un String.

Un programme dans Ruby serait-il plus léger et / ou plus rapide que son homologue, disons Python ou Node? Si oui, serait-ce à cause de cela symbols?

Je ne suis pas sûr de ce que vous demandez ici. Les performances sont principalement une question de qualité de mise en œuvre, pas de langue. De plus, Node n'est même pas un langage, c'est un cadre d'E / S à événements pour ECMAScript. En exécutant un script équivalent sur IronPython et MRI, IronPython est susceptible d'être plus rapide. Exécuter un script équivalent sur CPython et JRuby + Truffle, JRuby + Truffle est susceptible d'être plus rapide. Cela n'a rien à voir avec Symbols mais avec la qualité de l'implémentation: JRuby + Truffle a un compilateur optimisant de manière agressive, ainsi que toute la machinerie d'optimisation d'une machine virtuelle Java haute performance, CPython est un simple interprète.

Étant donné que l'une des intentions de Ruby est d'être facile à lire et à écrire pour les humains, ses créateurs ne pourraient-ils pas faciliter le processus de codage en mettant en œuvre ces améliorations dans l'interpréteur lui-même (comme cela pourrait être dans d'autres langues)?

Les n ° Symbolne sont pas une optimisation du compilateur. Il s'agit d'un type de données distinct avec une sémantique spécifique. Ils ne sont pas comme les flonums de YARV , qui sont une optimisation interne privée pour l' alFloat . La situation est la même que pour Integer, Bignumet Fixnumqui devrait être un détail d'optimisation interne privée invisible, mais est malheureusement pas. (Ce va enfin être fixé dans Ruby 2.4, qui enlève Fixnumet Bignumet feuilles seulement Integer.)

Le faire comme Java le fait, en tant qu'état spécial de Strings normal signifie que vous devez toujours vous méfier de savoir si vos Strings sont dans cet état spécial et dans quelles circonstances ils sont automatiquement dans cet état spécial et quand ce n'est pas le cas. C'est une charge beaucoup plus élevée que d'avoir simplement un type de données séparé.

Y aurait-il une définition indépendante de la langue des symboles et une raison de les avoir dans d'autres langues?

Symbolest un type de données qui désigne le concept de nom ou d' étiquette . Symbols sont des objets de valeur , immuables, généralement immédiats (si le langage distingue une telle chose), apatrides, et n'ont pas d'identité. Deux Symbols qui sont égaux sont également garantis identiques, en d'autres termes, deux Symbols qui sont égaux sont en fait les mêmes Symbol. Cela signifie que l'égalité de valeur et l'égalité de référence sont la même chose, et donc l'égalité est efficace et O (1).

Les raisons de les avoir dans une langue sont vraiment les mêmes, indépendamment de la langue. Certaines langues en dépendent plus que d'autres.

Dans la famille Lisp, par exemple, il n'y a pas de concept de "variable". Au lieu de cela, vous avez des Symbols associés aux valeurs.

Dans les langues avec des capacités de réflexion ou introspectives, Symbols sont souvent utilisés pour désigner les noms des entités reflétées dans les API de réflexion, par exemple dans Ruby, Object#methods, Object#singleton_methods, Object#public_methods, Object#protected_methodset Object#public_methodsretourner un Arrayde Symbols (bien qu'ils pourraient tout aussi bien retourner un Arrayde Methods). Object#public_sendprend un Symboldénotant le nom du message à envoyer comme argument (bien qu'il accepte également un Stringaussi, Symbolest plus sémantiquement correct).

Dans ECMAScript, les Symbols sont un élément fondamental pour rendre ECMAScript plus sûr à l'avenir. Ils jouent également un grand rôle dans la réflexion.


Les atomes d'Erlang ont été pris directement de Prolog (Robert Virding me l'a dit à un moment donné)
Zachary K

2

Les symboles sont utiles dans Ruby, et vous les verrez partout dans le code Ruby car chaque symbole est réutilisé chaque fois qu'il est référencé. Il s'agit d'une amélioration des performances par rapport aux chaînes, car chaque utilisation d'une chaîne qui n'est pas enregistrée dans une variable crée un nouvel objet en mémoire. Par exemple, si j'utilise plusieurs fois la même chaîne comme clé de hachage:

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

La chaîne "a" est créée 101 000 fois en mémoire. Si j'ai utilisé un symbole à la place:

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

Le symbole :aest toujours un objet en mémoire. Cela rend les symboles beaucoup plus efficaces que les chaînes.

MISE À JOUR Voici une référence (tirée de Codecademy ) qui démontre la différence de performances:

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."

Voici mes résultats pour mon MBP:

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

Il y a une différence claire entre l'utilisation de chaînes et de symboles pour simplement identifier les clés dans un hachage.


Je ne sais pas si c'est le cas. Je m'attendrais à ce qu'une implémentation Ruby exécute le même code plusieurs fois, sans analyser le code encore et encore pour chaque itération. Même si chaque occurrence lexicale de "a"est en effet une nouvelle chaîne, je pense que dans votre exemple, il y en aura exactement deux "a"(et une implémentation pourrait même partager la mémoire jusqu'à ce que l'une d'entre elles soit mutée). Afin de créer des millions de chaînes, vous devrez probablement utiliser String.new ("a"). Mais je ne connais pas bien Ruby, alors peut-être que je me trompe.
coredump

1
Dans l'une des leçons de Codecademy, ils génèrent une référence pour les chaînes par rapport aux symboles, un peu comme mon exemple. Je vais l'ajouter à la réponse.
Keith Mattix

1
Merci d'avoir ajouté la référence. Votre test montre le gain attendu obtenu en utilisant des symboles au lieu de chaînes, en raison d'un test plus rapide dans la table de hachage (identité vs comparaison de chaînes), mais il n'y a aucun moyen de déduire que les chaînes sont allouées à chaque itération. J'ai ajouté une version avec string_AZ[String.new("r")]pour voir si cela fait une différence. J'obtiens 21 ms pour les cordes (version originale), 7 ms avec des symboles et 50 ms avec de nouvelles cordes à chaque fois. Je dirais donc que les chaînes ne sont pas allouées autant avec la "r"version littérale .
coredump

1
Ah, j'ai donc creusé un peu plus, et dans Ruby 2.1, les chaînes sont en fait partagées. J'ai apparemment raté cette mise à jour; Merci d'avoir fait remarquer cela. Pour en revenir à la question d'origine, je pense que les deux repères montrent l'utilité des symboles par rapport aux chaînes.
Keith Mattix
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.