Qu'est-ce qu'un «symbole» chez Julia?

131

Plus précisément: j'essaie d'utiliser le package DataFrames de Julia, en particulier la fonction readtable () avec l'option names, mais cela nécessite un vecteur de symboles.

  • qu'est-ce qu'un symbole?
  • pourquoi choisiraient-ils cela sur un vecteur de chaînes?

Jusqu'à présent, je n'ai trouvé qu'une poignée de références au mot symbole dans la langue Julia. Il semble que les symboles soient représentés par ": var", mais ce qu'ils sont est loin d'être clair.

A part: je peux courir

df = readtable( "table.txt", names = [symbol("var1"), symbol("var2")] )

Mes deux questions à puces sont toujours d'actualité.

Mageek
la source
3
Certaines conversations sur ce sujet peuvent être trouvées ici: groups.google.com/d/msg/julia-users/MS7KW8IU-0o/cQ-yDOs_CQEJ
jverzani

Réponses:

231

Les symboles dans Julia sont les mêmes que dans Lisp, Scheme ou Ruby. Cependant, les réponses à ces questions connexes ne sont pas vraiment satisfaisantes , à mon avis. Si vous lisez ces réponses, il semble que la raison pour laquelle un symbole est différent d'une chaîne est que les chaînes sont mutables tandis que les symboles sont immuables, et que les symboles sont également «internés» - quoi que cela signifie. Il se trouve que les chaînes sont mutables dans Ruby et Lisp, mais elles ne le sont pas dans Julia, et cette différence est en fait un hareng rouge. Le fait que les symboles soient internés - c'est-à-dire hachés par l'implémentation du langage pour des comparaisons d'égalité rapides - est également un détail d'implémentation non pertinent. Vous pourriez avoir une implémentation qui n'intègre pas de symboles et le langage serait exactement le même.

Alors, qu'est-ce qu'un symbole, vraiment? La réponse réside dans quelque chose que Julia et Lisp ont en commun - la capacité de représenter le code du langage comme une structure de données dans le langage lui-même. Certaines personnes appellent cela «homoiconicité» ( Wikipedia ), mais d'autres ne semblent pas penser que cela suffit à lui seul pour qu'une langue soit homoïconique. Mais la terminologie n'a pas vraiment d'importance. Le fait est que lorsqu'un langage peut représenter son propre code, il a besoin d'un moyen de représenter des choses comme des affectations, des appels de fonction, des choses qui peuvent être écrites comme des valeurs littérales, etc. Il a également besoin d'un moyen de représenter ses propres variables. Ie, vous avez besoin d'un moyen de représenter - sous forme de données - le foosur le côté gauche de ceci:

foo == "foo"

Nous entrons maintenant dans le vif du sujet: la différence entre un symbole et une chaîne est la différence entre foole côté gauche de cette comparaison et "foo"le côté droit. Sur la gauche, se footrouve un identificateur et il évalue la valeur liée à la variable foodans la portée actuelle. Sur la droite, se "foo"trouve un littéral de chaîne et il évalue la valeur de chaîne "foo". Un symbole dans Lisp et Julia est la façon dont vous représentez une variable sous forme de données. Une chaîne se représente simplement. Vous pouvez voir la différence en appliquant evalà eux:

julia> eval(:foo)
ERROR: foo not defined

julia> foo = "hello"
"hello"

julia> eval(:foo)
"hello"

julia> eval("foo")
"foo"

Ce que le symbole :fooévalue dépend de ce à quoi - le cas échéant - la variable fooest liée, alors que "foo"toujours évalue juste à "foo". Si vous souhaitez créer des expressions dans Julia qui utilisent des variables, vous utilisez des symboles (que vous le sachiez ou non). Par exemple:

julia> ex = :(foo = "bar")
:(foo = "bar")

julia> dump(ex)
Expr
  head: Symbol =
  args: Array{Any}((2,))
    1: Symbol foo
    2: String "bar"
  typ: Any

Ce que cela montre, entre autres, c'est qu'il y a un :fooobjet symbole à l'intérieur de l'objet d'expression que vous obtenez en citant le code foo = "bar". Voici un autre exemple, construction d'une expression avec le symbole :foostocké dans la variable sym:

julia> sym = :foo
:foo

julia> eval(sym)
"hello"

julia> ex = :($sym = "bar"; 1 + 2)
:(begin
        foo = "bar"
        1 + 2
    end)

julia> eval(ex)
3

julia> foo
"bar"

Si vous essayez de le faire quand symest lié à la chaîne "foo", cela ne fonctionnera pas:

julia> sym = "foo"
"foo"

julia> ex = :($sym = "bar"; 1 + 2)
:(begin
        "foo" = "bar"
        1 + 2
    end)

julia> eval(ex)
ERROR: syntax: invalid assignment location ""foo""

Il est assez clair de voir pourquoi cela ne fonctionnera pas - si vous avez essayé d'attribuer "foo" = "bar"manuellement, cela ne fonctionnera pas non plus.

C'est l'essence d'un symbole: un symbole est utilisé pour représenter une variable en métaprogrammation. Une fois que vous avez des symboles comme type de données, bien sûr, il devient tentant de les utiliser pour d'autres choses, comme des clés de hachage. Mais c'est une utilisation fortuite et opportuniste d'un type de données qui a un autre objectif principal.

Notez que j'ai arrêté de parler de Ruby il y a quelque temps. C'est parce que Ruby n'est pas homoiconique: Ruby ne représente pas ses expressions comme des objets Ruby. Le type de symbole de Ruby est donc une sorte d'organe résiduel - une adaptation restante, héritée de Lisp, mais qui n'est plus utilisée pour son objectif initial. Les symboles Ruby ont été cooptés à d'autres fins - en tant que clés de hachage, pour extraire des méthodes des tables de méthodes - mais les symboles en Ruby ne sont pas utilisés pour représenter des variables.

Quant à savoir pourquoi les symboles sont utilisés dans les DataFrames plutôt que les chaînes, c'est parce que c'est un modèle commun dans DataFrames pour lier les valeurs de colonne à des variables à l'intérieur des expressions fournies par l'utilisateur. Il est donc naturel que les noms de colonnes soient des symboles, car les symboles sont exactement ce que vous utilisez pour représenter des variables sous forme de données. Actuellement, vous devez écrire df[:foo]pour accéder à la foocolonne, mais à l'avenir, vous pourrez peut-être y accéder à la df.fooplace. Lorsque cela devient possible, seules les colonnes dont les noms sont des identificateurs valides seront accessibles avec cette syntaxe pratique.

Voir également:

StefanKarpinski
la source
6
Interne: en informatique, l'internalisation de chaînes est une méthode de stockage d'une seule copie de chaque valeur de chaîne distincte, qui doit être immuable. L'intégration de chaînes rend certaines tâches de traitement de chaînes plus efficaces en termes de temps ou d'espace, au prix de nécessiter plus de temps lorsque la chaîne est créée ou internée. en.wikipedia.org/wiki/String_interning
xiaodai
À un moment donné, vous écrivez eval(:foo)et à un autre eval(sym). Y a-t-il une différence significative entre eval(:foo)et eval(foo)?
Niveaux de gris
Tout à fait: eval(:foo)donne la valeur à laquelle la variable fooest liée alors que l' eval(foo)appelle eval sur cette valeur. L'écriture eval(:foo)est équivalente à juste foo(dans la portée globale), eval(foo)c'est comme ça eval(eval(:foo)).
StefanKarpinski