Interêt du Type Symbol en Scala
Présentation du type Symbol
La classe Symbol est une classe de la bibliothèque standard du langage Scala. Il permet de façon simple de créer un objet unique pour une chaîne de caractères donnée. La comparaison de 2 symbols est une simple comparaison de références. On dit que l’objet est interné.
Pour simplifier son usage, le compilateur Scala reconnait l’instanciation d’un Symbol pour tous les identifiants préfixés par un simple quote '.
Exemple d’utilisation :
Symbol("test") == 'test
Interêt
Les symbols sont des éléments indispensable pour un langage tel Lisp qui les utilise pour représenter toutes sortes d’objet : classe, méthodes, variables, etc… Lisp permet de mélanger code et donnée de façon totalement transparente et des manipuler les uns ou les autres de la même manière. En Lisp, Les symbols sont aussi utilisés par exemple pour imiter des énumérations.
En Scala il est impossible d’utiliser les symbols pour identifier une méthode par exemple (comme ce que l’on ferait en Lisp). Les symbols perdent donc pas mal de leur interêt…
On pourrait se dire qu’en utilisant des symbols comme identifiants, on peut gagner de la place par rapport à des chaînes de caractères, mais alors pourquoi ne pas utiliser directement un enum, ou dans des cas plus évolué une case class ? C’est peut être intéressant pour gagner du temps, cela évite de déclarer un enum. Ou bien une utilisation dans une table de hashage comme identifiants à la place de string, mais là encore même chose, les strings litérales (cad définies via le biais de double quote, "ma chaine") sont internées (voir suite de l’article) et donc cela ne prend pas plus de mémoire et les comparaisons sont aussi rapides.
Et surtout si vous trouvez un avantage majeur à utiliser les Symbols, postez un commentaire, je suis curieux de trouver un vrai cas d’utilisation.
Exemple décompilé
La suite de l’article s’adresse à ceux qui cherchent à voir en détails ce qui se passe dans la machine virtuelle. Le but est de comprendre comment est représenté une string litteral comment le compilateur les interne.
Source d’un exemple tout simple en scala
class SymbolTest { def run = { "test" == "test" } }
Décompilation avec la commande javap
$ javap -c -verbose scalafr.StringTest
Cette commande produit la sortie suivante (j’ai supprimé les lignes qui ne sont pas pertinentes à la démonstration) :
[...] // Pool de constantes const #7 = Asciz java/lang/Object; const #8 = class #7; // java/lang/Object [...] const #13 = Asciz test; const #14 = String #13; // test const #15 = Asciz equals; const #16 = Asciz (Ljava/lang/Object;)Z; const #17 = NameAndType #15:#16;// equals:(Ljava/lang/Object;)Z const #18 = Method #8.#17; // java/lang/Object.equals:(Ljava/lang/Object;)Z [...] { public scalafr.StringTest(); [...] // Code de la méthode run() public boolean run(); Code: 0: ldc #14; //String test 2: ldc #14; //String test 4: astore_1 [...] 17: aload_1 18: invokevirtual #18; //Method java/lang/Object.equals:(Ljava/lang/Object;)Z [...]
La commande ldc prend une référence du pool de constantes et la pose sur la pile d’opérande. Le pool de constantes est représenté au dessus du code, ldc #14va chercher const #14 et la pose sur la pile. La ligne 18 invoque la méthode réprésenté par #18 (dans ce cas, java.lang.Object.equals()) en utilisant les 2 derniers éléments posés sur la pile (les ldc des lignes 19 et 20. On remarque que ces deux ldc charge la même réference de constante : #14 qui représente la chaîne de caractère « test », c’est ça l’internement de string litérales.
Pour aller plus loin :
Les Symbols peuvent être très utile lors de l’implémentation d’un DSL interne (internal Domain Specific Language).
L’exemple suivant en fait un usage intéressant:
http://blog.fogus.me/2009/03/26/baysick-a-scala-dsl-implementing-basic/
Les source sont dispos ici:
http://github.com/fogus/baysick
« Le type Symbol est un type particulier intégré au langage Scala. Il permet de façon simple de créer un objet unique pour une chaîne de caractères particulière. La comparaison de 2 symbols est une simple comparaison de références. »
Il y a *plusieurs* bourdes dans cette phrase :
* le type Symbol n’est absolument pas un « type » particulier. Il s’agit simplement de la classe scala.Symbol
* la syntaxe « symbolLiteral ::= ‘’’ idrest » n’est que du sucre syntaxique pour l’instanciation d’un objet de cette classe (cf. §1.3.7 du manuel de référence)
* l’objet n’est absolument pas unique et est instancié
* le système de type n’a pas un comportement particulier pour les symboles. Ils sont typés comme n’importe quelle autre classe
* bien entendu, la comparaison ne se fait pas par référence. D’ailleurs, « == » n’est pas un opérateur mais une fonction comme une autre, correspondant à la méthode equals de Java.
Pour prouver tout ça, il suffit de vérifier tout ça dans l’interpréteur :
scala> Symbol( »to »+ »to ») == Symbol( »toto »)
res1: Boolean = true
scala> Symbol( »toto ») == ‘toto
res2: Boolean = true
Alexandre.
@Alexandre : Merci pour ton commentaire. J’ai seulement modifié le début de la phrase pour qu’elle soit plus explicite. Le reste de l’article est correct et va dans le même sens que tes remarques. L’instance est bien unique pour une chaine donnée (doc officielle : « This class provides a simple way to get unique objects for equal strings »).