13 perspectives sur les symboles de Ruby
Les nouveaux développeurs Ruby demandent souvent : « Les symboles, c’est
quoi, exactement ? Et quelle est la différence entre String
et Symbol
?
» Malheureusement, il n’y a aucune explication qui va aider tout le monde,
alors — avec mes excuses au poète américain Wallace Stevens — je
vous présente 13 perspectives sur les symboles de Ruby.
Un symbole de Ruby est :
- …le nom de quelque chose, et pas seulement un morceau de texte
- …une constante dans une énumération informelle
- …un nom constant et unique
- …une chaîne de caractères « internée »
- …un objet qui peut être comparé en temps O(1)
- …un identificateur Lisp
- …un identificateur Ruby
- …le mot-clé d’un paramètre mot-clé
- …un bon choix pour une clé de hachage
- …comme un OSType sur le Mac
- …une fuite de mémoire
- …une façon astucieuse de ne garder en mémoire qu’un seul exemplaire d’une chaîne
- …un
typedef
C appeléID
1. Un symbole est le nom de quelque chose, et pas seulement un morceau de texte
En Ruby, on utilise un symbole quand on a besoin d’identifier quelque chose par un nom :
find_text(:parler_pour_ne_rien_dire)
Mais pour représenter des textes, on utilise des chaînes :
"Je vous signale tout de suite que je vais parler pour ne rien dire…"
2. Un symbole est une constante dans une énumération informelle
En C++ (et dans beaucoup d’autres langages), on peut utiliser une énumération pour représenter une famille de constantes apparentées :
enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status = CLOSED;
Mais puisque Ruby est un langage dynamique, on n’a pas besoin de déclarer
un type BugStatus
, ou de maintenir une liste de valeurs permises.
À la place, on représente les valeurs de l’énumération avec des symboles :
original_status = :open
current_status = :closed
3. Un symbole est un nom constant et unique
En Ruby, on peut changer le contenu d’une chaîne :
"foo"[0] = ?b # "boo"
Mais on ne peut pas changer le contenu d’un symbole :
:foo[0] = ?b # Lève une exception.
De la même façon, on peut avoir deux chaînes différentes avec le même contenu :
# Le même contenu, mais deux chaînes différentes.
"open".object_id != "open".object_id
Mais deux symboles avec le même nom font toujours référence au même objet :
# Le même nom, le même objet.
:open.object_id == :open.object_id
4. Un symbole est une chaîne de caractères « internée »
En Ruby, on peut convertir une chaîne en symbole en utilisant la méthode
intern
:
"foo".intern # Renvoie :foo
intern
maintient une table de hachage qui associe qui associe les chaînes
à leurs symboles correspondants. La première fois qu’elle voit une chaîne,
elle crée un nouveau symbole, et le met dans la table de hachage. Les fois
suivantes, quand intern
voit la chaîne, elle cherche le symbole initial
dans la table.
Nous pourrions implémenter notre propre version de Symbol
et d’intern
avec le code suivant :
class MySymbol
TABLE={}
def initialize(str) @str = str end
def to_s() @str end
def ==(other)
self.object_id == other.object_id
end
end
class String
def my_intern()
table = MySymbol::TABLE
unless table.has_key?(self)
table[self] = MySymbol.new(self)
end
table[self]
end
end
"foo".my_intern
5. Un symbole est un objet qui peut être comparé en temps O(1)
Si on veut comparer deux chaînes, on aura potentiellement besoin de regarder chaque caractère. Dans le cas de deux chaînes de longueur N, il nous faudra N+1 comparaisons, ce que les informaticiens appellent « temps O(N) ».
def string_comp(str1, str2)
return false if str1.length != str2.length
for i in 0...str1.length
return false if str1[i] != str2[i]
end
return true
end
string_comp("foo", "foo")
Mais puisque chaque occurrence de :foo
se réfère au même objet, on peut
comparer deux symboles en examinant leurs identificateurs d’objet. On peut
le faire avec une seule comparaison, ce que les informaticiens appellent «
temps O(1) ».
def symbol_comp(sym1, sym2)
sym1.object_id == sym2.object_id
end
symbol_comp(:foo, :foo)
6. Un symbole est un identificateur Lisp
Les ancêtres de symboles Ruby sont les symboles Lisp. En Lisp, les symboles
sont utilisés pour représenter les « identificateurs » (les noms de
variables et de fonctions) dans un programme après analyse syntaxique.
Supposons qu’on a un fichier appelé double.l
qui contient une seule
fonction :
(defun double (x)
(* x 2))
On peut analyser ce fichier avec read
:
(read "double.l")
;; Renvoie '(defun double (x) (* x 2))
Cette invocation renvoie une liste imbriquée qui contient les symboles
defun
, double
, *
, x
(deux fois) et le nombre 2
.
7. Un symbole est un identificateur Ruby
En Ruby, on peut chercher des identificateurs (les noms de variables, de fonctions et de constantes) pendant l’exécution d’un programme. D’habitude, on le fait en utilisant des symboles.
class Demo
# Les trucs qu'on va chercher.
DEFAULT = "Hello"
def initialize()
@message = DEFAULT
end
def say() @message end
# Utiliser symboles pour chercher identificateurs.
def look_up_with_symbols()
[Demo.const_get(:DEFAULT),
method(:say),
instance_variable_get(:@message)]
end
end
Demo.new.look_up_with_symbols
8. Un symbole est le mot-clé d’un paramètre mot-clé
Quand on passe des arguments mot-clé à une fonction Ruby, on peut spécifier les mots-clés avec des symboles :
# Construire une URL pour l'objet 'bug' avec Rails.
url_for(:controller => 'bug',
:action => 'show',
:id => bug.id)
9. Un symbole est un bon choix pour une clé de hachage
D’habitude, on utilise des symboles pour représenter les clés d’une table de hachage :
options = {}
options[:auto_save] = true
options[:show_comments] = false
10. Un symbole est comme un OSType sur le Mac
Avant l’arrivée de MacOS X (et même un peu après), les anciennes versions du système d’exploitation utilisaient des abréviations de quatre caractères pour représenter des énumérations « extensibles » :
enum {
kSystemFolderType = 'macs',
kDesktopFolderType = 'desk',
// ...etc...
kTrashFolderType = 'trsh'
};
OSType folder = kSystemFolderType;
En Ruby, on utiliserait des symboles dans un contexte similaire :
:system_folder
:desktop_folder
:trash_folder
11. Un symbole est une fuite de mémoire
En raison de la manière dont sont stockés les symboles Ruby, ils ne peuvent jamais être recyclés par le ramasse-miettes [en]. Alors, si on crée 10 000 symboles qu’on n’utilisera plus jamais, on ne récupèra jamais la mémoire.
Certaines implémentions de Scheme utilisent une version astucieuse
d’intern
qui stocke des symboles dans une table de hachage à référence
faible. Cela permet de ramasser les symboles sans perdre leurs propriétés
d’unicité.
12. Un symbole est une façon astucieuse de ne garder en mémoire qu’un seul exemplaire d’une chaîne
(Pour une idée similaire, voir cet article [en].)
Supposons qu’on travaille sur un analyseur de langage naturel qui essaye de comprendre des commandes de petit déjeuner. On a un corpus de 30 000 phrases qui représentent les commandes d’un restaurant typique, et on va essayer de trouver les motifs.
Mais même si on a un très grand nombre de phrases, le vocabulaire lui-même est très limité. On ne veut pas garder 15 000 exemplaires du mot « bacon » en mémoire ! À la place, on peut utiliser des symboles pour représenter les mots individuels :
corpus = [
[:i, :want, :some, :bacon],
[:i, :want, :some, :eggs],
[:give, :me, :some, :bacon],
[:chunky, :bacon],
# ... 29 995 phrases en plus ...
[:some, :toast, :please]
]
Aux débuts de l’IA, beaucoup de programmes Lisp utilisaient la même stratégie pour représenter des textes.
13. Un symbole est un typedef
C appelé ID
Au niveau interne, Ruby 1.8 représente des symboles avec le type ID
.
Cela est un typedef
pour un entier non-signé. (C’est un peu plus
compliqué avec la version 2.1, mais l’idée reste la même.) Un ID
représente une entrée dans la table de symboles de Ruby.
typedef unsigned long ID;
Voici quelques fonctions intéressantes qui concernent les symboles :
// Entrer une chaîne C dans la table de symboles.
ID rb_intern(const char *name);
// Convertir un ID en objet symbole.
#define ID2SYM(x)
// Convertir une chaîne en objet symbole.
VALUE rb_str_intern(VALUE s);
Autres explications de symboles Ruby
Si aucune de ces explications ne vous aide, peut-être aurez-vous plus de chance avec l’une de celles-ci :
- Expliquez-moi : les symboles du langage Ruby
- Symbols Are Not Immutable Strings
- Using Symbols for the Wrong Reasons
- Yet Another Blog About Ruby Symbols
- Digging into Ruby Symbols
- Understanding Ruby Symbols
(Merci à Alex, Lisa et Henri pour leurs corrections.)
Voulez-vous me contacter au sujet de ce post ? Ou si vous cherchez quelque chose à lire, voilà une liste de posts populaires.