Comment comprendre les symboles dans Ruby

85

Malgré la lecture de " Understanding Ruby Symbols ", je suis toujours confus par la représentation des données en mémoire quand il s'agit d'utiliser des symboles. Si un symbole, dont deux contenus dans des objets différents, existe dans le même emplacement mémoire, comment se fait-il qu'ils contiennent des valeurs différentes ? Je m'attendais à ce que le même emplacement mémoire contienne la même valeur.

Voici une citation du lien:

Contrairement aux chaînes, les symboles du même nom sont initialisés et n'existent en mémoire qu'une seule fois lors d'une session de ruby

Je ne comprends pas comment il parvient à différencier les valeurs contenues dans le même emplacement mémoire.

Prenons cet exemple:

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

patient1et patient2sont tous les deux des hachages, c'est très bien. :rubycependant est un symbole. Si nous devions produire ce qui suit:

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

Alors qu'est-ce qui sera produit? "red", ou "programming"?

Oubliant les hachages pendant une seconde, je pense qu'un symbole est un pointeur vers une valeur. Les questions que j'ai sont:

  • Puis-je attribuer une valeur à un symbole?
  • Un symbole est-il juste un pointeur vers une variable contenant une valeur?
  • Si les symboles sont globaux, cela signifie-t-il qu'un symbole pointe toujours vers une chose?
Kezzer
la source
1
Il affichera ": ruby", car vous imprimez un symbole. Si vous dites puts patient1[:ruby], il imprimera "rouge", si vous dites puts patient2[:ruby], il imprimera "programmation".
ROSS
1
Un symbole n'est PAS un pointeur vers une valeur. En interne, un symbole n'est qu'un entier.
akuhn

Réponses:

62

Considère ceci:

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

Ainsi, quelle que soit la manière dont vous créez un objet symbole, tant que son contenu est le même, il fera référence au même objet en mémoire. Ce n'est pas un problème car un symbole est un objet immuable . Les chaînes sont mutables.


(En réponse au commentaire ci-dessous)

Dans l'article d'origine, la valeur n'est pas stockée dans un symbole, elle est stockée dans un hachage. Considère ceci:

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

Cela crée six objets dans la mémoire - quatre objets chaîne et deux objets de hachage.

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

Cela ne crée que cinq objets en mémoire - un symbole, deux chaînes et deux objets de hachage.

Anshul
la source
L'exemple du lien, cependant, montre les symboles contenant des valeurs différentes , mais le symbole a le même nom et le même emplacement mémoire. Quand ils sont sortis, ils ont des valeurs différentes , c'est la partie que je ne comprends pas. Ils devraient sûrement contenir la même valeur?
Kezzer le
1
Je viens de faire une modification pour essayer d'expliquer à quel point je suis encore confus. Mon cerveau ne peut pas calculer;)
Kezzer
48
Les symboles ne contiennent pas de valeurs, ce sont des valeurs. Les hachages contiennent des valeurs.
Mladen Jablanović
5
C'est le Hash(créé par {... => ...} dans votre code) qui stocke les paires clé / valeur, pas les Symbols eux-mêmes. Les Symbols (par exemple :symbolou :symou :ruby) sont les clés des paires. Ce n'est que dans le cadre d'un Hash"point" vers quoi que ce soit.
James A. Rosen
1
Le symbole est utilisé comme clé dans le hachage et non comme valeur, c'est pourquoi ils peuvent être différents, c'est similaire à utiliser en disant key1 = 'ruby' et hash1 = {key1 => 'value' ...} hash2 = { key1 => 'valeur2' ...}.
Joshua Olson
53

J'étais capable de grogner des symboles quand j'y pensais comme ça. Une chaîne Ruby est un objet qui a un tas de méthodes et de propriétés. Les gens aiment utiliser des chaînes pour les clés, et lorsque la chaîne est utilisée pour une clé, toutes ces méthodes supplémentaires ne sont pas utilisées. Ils ont donc créé des symboles, qui sont des objets de chaîne avec toutes les fonctionnalités supprimées, sauf ce qui est nécessaire pour que ce soit une bonne clé.

Pensez simplement aux symboles comme des chaînes constantes.

Segfault
la source
2
En lisant les messages, celui-ci a probablement le plus de sens pour moi. : ruby ​​est juste stocké quelque part en mémoire, si j'utilise "ruby" quelque part, puis "ruby" à nouveau quelque part, c'est juste une duplication. L'utilisation de symboles est donc un moyen de réduire la duplication des données communes. Comme vous le dites, des chaînes constantes. Je suppose qu'il y a un mécanisme sous-jacent qui retrouvera ce symbole à utiliser?
Kezzer
@Kezzer Cette réponse est vraiment bonne et me semble juste, mais votre commentaire dit quelque chose de différent et est faux ou trompeur, votre commentaire parle de duplication de données avec des chaînes, et que c'est la raison des symboles, c'est faux ou trompeur. le symbole plusieurs fois n'utilisera pas plus d'espace mémoire, mais vous pouvez aussi l'avoir pour les chaînes dans de nombreux langages, par exemple certains langages de programmation si vous écrivez "abc" et ailleurs "abc" le compilateur voit que c'est la même chaîne de valeur et la stocke au même endroit, ce qui en fait le même objet, cela s'appelle la chaîne interne et c # le fait.
barlop
C'est donc fondamentalement une version incroyablement légère d'une corde?
stevec
34

Le symbole :rubyne contient pas "red"ou "programming". Le symbole :rubyn'est que le symbole :ruby. Ce sont vos hachages, patient1et patient2que chacun contient ces valeurs, dans chaque cas pointé par la même clé.

Pensez-y de cette façon: si vous entrez dans le salon le matin de Noël et que vous voyez deux boîtes avec une étiquette sur elles qui disent "Kezzer". On a des chaussettes dedans et l'autre a du charbon. Vous n'allez pas être confus et demander comment "Kezzer" peut contenir à la fois des chaussettes et du charbon, même s'il porte le même nom. Parce que le nom ne contient pas les cadeaux (merdiques). Il ne fait que les pointer du doigt. De même, :rubyne contient pas les valeurs dans votre hachage, il pointe simplement vers elles.

jcdyer
la source
2
Cette réponse est parfaitement logique.
Vass
Cela ressemble à un mélange total de hachages et de symboles. Un symbole ne pointe pas vers une valeur, si vous voulez dire qu'il le fait dans un hachage, eh bien, cela peut être discutable, mais un symbole n'a pas besoin d'être dans un hachage. Vous pouvez dire que mystring = :steveT le symbole ne pointe vers rien. Une clé dans un hachage a une valeur associée et la clé peut être un symbole. Mais un symbole n'a pas besoin d'être dans un hachage.
barlop
27

Vous pourriez supposer que la déclaration que vous avez faite définit la valeur d'un symbole comme étant autre chose que ce qu'il est. En fait, un symbole n'est qu'une valeur de chaîne "internalisée" qui reste constante. C'est parce qu'ils sont stockés à l'aide d'un simple identifiant entier qu'ils sont fréquemment utilisés car c'est plus efficace que de gérer un grand nombre de chaînes de longueur variable.

Prenons le cas de votre exemple:

patient1 = { :ruby => "red" }

Cela doit être lu comme suit: "déclarer une variable patient1 et la définir comme un Hash, et dans ce magasin la valeur 'red' sous la clé (symbole 'ruby')"

Une autre façon d'écrire ceci est:

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

puts patient1[:ruby]
# 'red'

Lorsque vous effectuez une mission, il n'est guère surprenant que le résultat obtenu soit identique à ce que vous lui avez attribué en premier lieu.

Le concept de symbole peut être un peu déroutant car ce n'est pas une caractéristique de la plupart des autres langues.

Chaque objet String est distinct même si les valeurs sont identiques:

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

Chaque symbole avec la même valeur fait référence au même objet:

[ :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

La conversion de chaînes en symboles mappe des valeurs identiques sur le même symbole unique:

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

De même, la conversion de Symbol en String crée une chaîne distincte à chaque fois:

[ :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

Vous pouvez considérer les valeurs de symbole comme étant tirées d'une table de hachage interne et vous pouvez voir toutes les valeurs qui ont été encodées en symboles à l'aide d'un simple appel de méthode:

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=, ...

Au fur et à mesure que vous définissez de nouveaux symboles par la notation deux-points ou en utilisant .to_sym, cette table grandira.

tadman
la source
17

Les symboles ne sont pas des pointeurs. Ils ne contiennent pas de valeurs. Les symboles le sont tout simplement . :rubyest le symbole :rubyet c'est tout ce qu'il y a à faire. Il ne contient pas de valeur, il ne fait rien, il existe juste comme symbole :ruby. Le symbole :rubyest une valeur comme le chiffre 1. Il ne pointe pas plus vers une autre valeur que le chiffre 1.

Mandrin
la source
13
patient1.each_key {|key| puts key.to_s}

Alors qu'est-ce qui sera produit? "rouge" ou "programmation"?

Ni l'un ni l'autre, il affichera "ruby".

Vous confondez symboles et hachages. Ils ne sont pas liés, mais ils sont utiles ensemble. Le symbole en question est :ruby; cela n'a rien à voir avec les valeurs du hachage, et sa représentation en entier interne sera toujours la même, et sa "valeur" (lorsqu'elle est convertie en chaîne) sera toujours "ruby".

meagar
la source
10

En bref

Les symboles résolvent le problème de la création de représentations immuables et lisibles par l'homme, qui présentent également l'avantage d'être plus simples à rechercher au moment de l'exécution que des chaînes. Pensez-y comme un nom ou une étiquette qui peut être réutilisé.

Pourquoi: le rouge est meilleur que le "rouge"

Dans les langages dynamiques orientés objet, vous créez des structures de données complexes et imbriquées avec des références lisibles. Le hachage est un cas d'utilisation courant où vous mappez des valeurs à des clés uniques - uniques, au moins, pour chaque instance. Vous ne pouvez pas avoir plus d'une clé «rouge» par hachage.

Cependant, il serait plus efficace pour le processeur d'utiliser un index numérique au lieu de clés de chaîne. Les symboles ont donc été introduits comme un compromis entre vitesse et lisibilité. Les symboles se résolvent beaucoup plus facilement que la chaîne équivalente. En étant lisibles par l'homme et faciles à résoudre pour le runtime, les symboles sont un complément idéal à un langage dynamique.

Avantages

Étant donné que les symboles sont immuables, ils peuvent être partagés au cours de l'exécution. Si deux instances de hachage ont un besoin lexicographique ou sémantique commun pour un élément rouge, le symbole: rouge utiliserait à peu près la moitié de la mémoire que la chaîne «red» aurait exigée pour deux hachages.

Puisque: le rouge résout toujours au même emplacement en mémoire, il peut être réutilisé sur une centaine d'instances de hachage avec presque aucune augmentation de mémoire, alors que l'utilisation de «rouge» ajoutera un coût de mémoire puisque chaque instance de hachage aurait besoin de stocker la chaîne mutable sur création.

Je ne sais pas comment Ruby implémente réellement les symboles / chaînes, mais il est clair qu'un symbole offre moins de surcharge d'implémentation dans le runtime car il s'agit d'une représentation fixe. Les symboles Plus nécessitent un caractère de moins à taper qu'une chaîne entre guillemets et moins de frappe est la poursuite éternelle des vrais rubisistes.

Sommaire

Avec un symbole comme: rouge, vous obtenez la lisibilité de la représentation sous forme de chaîne avec moins de frais généraux en raison du coût des opérations de comparaison de chaînes et de la nécessité de stocker chaque instance de chaîne en mémoire.

Mark Fox
la source
4

Je recommanderais de lire l'article de Wikipedia sur les tables de hachage - je pense que cela vous aidera à avoir une idée de ce que {:ruby => "red"}cela signifie vraiment.

Un autre exercice qui pourrait vous aider à comprendre la situation: réfléchissez {1 => "red"}. Sémantiquement, cela ne signifie pas "définir la valeur de 1sur "red"", ce qui est impossible dans Ruby. Cela signifie plutôt "créer un objet Hash et stocker la valeur "red"de la clé 1.

Greg Campbell
la source
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

patient1et patient2sont tous les deux des hachages, c'est très bien. :rubycependant est un symbole. Si nous devions produire ce qui suit:

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

Alors qu'est-ce qui sera produit? "rouge" ou "programmation"?

Ni l'un ni l'autre, bien sûr. La sortie sera ruby. Ce que, BTW, vous auriez pu découvrir en moins de temps qu'il ne vous en a fallu pour taper la question, en la tapant simplement dans IRB à la place.

Pourquoi serait- ce redou programming? Les symboles s'évaluent toujours par eux-mêmes. La valeur du symbole :rubyest le symbole :rubylui-même et la représentation sous forme de chaîne du symbole :rubyest la valeur de chaîne "ruby".

[BTW: putsconvertit toujours ses arguments en chaînes, de toute façon. Il n'est pas nécessaire de faire appel to_sà lui.]

Jörg W Mittag
la source
Je n'ai pas d'IRB sur la machine actuelle, je ne pourrais pas non plus l'installer, donc pourquoi, alors mes excuses pour cela.
Kezzer le
2
@Kezzer: Pas de soucis, j'étais juste curieux. Parfois, vous vous enfoncez si profondément dans un problème que vous ne pouvez même plus voir les choses les plus simples. Quand j'ai essentiellement coupé et collé votre question dans IRB, je me suis simplement demandé: "pourquoi n'a-t-il pas fait cela lui-même?" Et ne vous inquiétez pas, vous n'êtes pas le premier (et vous ne serez pas le dernier) à demander "qu'est-ce que cette impression" alors que la réponse est "lancez-le!" BTW: voici votre IRB instantané, n'importe où, n'importe quand, aucune installation nécessaire: TryRuby.Org Ou Ruby-Versions.Net vous donne un accès SSH à toutes les versions d'IRM jamais publiées + YARV + JRuby + Rubinius + REE.
Jörg W Mittag
Merci de jouer avec maintenant. Je suis encore un peu confus, alors j'y reviens.
Kezzer le
0

Je suis nouveau sur Ruby, mais je pense (espère?) Que c'est une façon simple de voir les choses ...

Un symbole n'est ni une variable ni une constante. Il ne représente pas ou n'indique pas une valeur. Un symbole EST une valeur.

Tout ce que c'est, c'est une chaîne sans la surcharge de l'objet. Le texte et seulement le texte.

Donc ça:

"hellobuddy"

Est-ce le même que ceci:

:hellobuddy

Sauf que vous ne pouvez pas faire, par exemple,: hellobuddy.upcase. C'est la valeur de la chaîne et UNIQUEMENT la valeur de la chaîne.

De même, ceci:

greeting =>"hellobuddy"

Est-ce le même que ceci:

greeting => :hellobuddy

Mais, encore une fois, sans la surcharge de l'objet chaîne.

Dave Munger
la source
-1

Une façon simple de comprendre cela est de penser: «Et si j'utilisais une chaîne plutôt qu'un symbole?

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

Ce n'est pas du tout déroutant, non? Vous utilisez "ruby" comme clé dans un hachage .

"ruby"est une chaîne littérale, c'est donc la valeur. L'adresse mémoire, ou pointeur, n'est pas disponible pour vous. Chaque fois que vous appelez "ruby", vous créez une nouvelle instance de celui-ci, c'est-à-dire une nouvelle cellule mémoire contenant la même valeur - "ruby".

Le hachage va alors "quelle est ma valeur de clé? Oh c'est "ruby". Puis mappe cette valeur à" rouge "ou" programmation ". En d'autres termes, :rubyne déréférencera pas à "red"ou "programming". Le hachage correspond :ruby à "red"ou "programming".

Comparez cela à si nous utilisons des symboles

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

La valeur de :rubyest aussi "ruby", effectivement.

Pourquoi? Parce que les symboles sont essentiellement des constantes de chaîne . Les constantes n'ont pas plusieurs instances. C'est la même adresse mémoire. Et une adresse mémoire a une certaine valeur, une fois déréférencée. Pour les symboles, le nom du pointeur est le symbole et la valeur déréférencée est une chaîne, qui correspond au nom du symbole, dans ce cas "ruby",.

Dans un hachage, vous n'utilisez pas le symbole, le pointeur, mais la valeur déférencée. Vous n'utilisez pas :ruby, mais "ruby". Le hachage recherche ensuite la clé "ruby", la valeur est "red"ou "programming", selon la façon dont vous avez défini le hachage.

Le changement de paradigme et le concept à emporter est que la valeur d'un symbole est un concept complètement distinct d'une valeur mappée par un hachage, étant donné une clé de ce hachage.

ahnbizcad
la source
Quelle est l'erreur ou l'erreur dans cette explication, les votes négatifs? curieux d'apprendre.
ahnbizcad
ce n'est pas parce qu'une analogie peut être désagréable pour certains qu'elle est défectueuse.
ahnbizcad
2
Je ne vois pas nécessairement d'erreurs, mais il est extrêmement difficile de cerner ce que vous essayez de dire dans cette réponse.
Reverse Engineered
x est intéressant et conceptualisé différemment en fonction du contexte / de l'entité qui interprète. assez simple.
ahnbizcad
révisé la réponse. Merci pour les commentaires.
ahnbizcad