Was ist ein "Symbol" in Julia?

130

Konkret: Ich versuche, Julias DataFrames-Paket zu verwenden, insbesondere die Funktion readtable () mit der Option names, aber dafür ist ein Vektor von Symbolen erforderlich.

  • Was ist ein Symbol?
  • warum sollten sie das einem Vektor von Strings vorziehen?

Bisher habe ich nur eine Handvoll Hinweise auf das Wortsymbol in der Julia-Sprache gefunden. Es scheint, dass Symbole durch ": var" dargestellt werden, aber mir ist alles andere als klar, was sie sind.

Nebenbei: Ich kann rennen

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

Meine zwei Fragen mit Aufzählungszeichen stehen noch.

Mageek
quelle
3
Einige Konversationen zu diesem Thema finden Sie hier: groups.google.com/d/msg/julia-users/MS7KW8IU-0o/cQ-yDOs_CQEJ
jverzani

Antworten:

230

Die Symbole in Julia sind dieselben wie in Lisp, Scheme oder Ruby. Die Antworten auf diese verwandten Fragen sind meiner Meinung nach jedoch nicht wirklich zufriedenstellend . Wenn Sie diese Antworten lesen, scheint der Grund, warum sich ein Symbol von einer Zeichenfolge unterscheidet, darin zu liegen, dass Zeichenfolgen veränderbar sind, während Symbole unveränderlich sind, und Symbole auch "interniert" werden - was auch immer das bedeutet. Saiten sind in Ruby und Lisp zwar veränderlich, aber in Julia nicht, und dieser Unterschied ist eigentlich ein roter Hering. Die Tatsache, dass Symbole interniert werden, dh von der Sprachimplementierung für schnelle Gleichheitsvergleiche gehasht werden, ist ebenfalls ein irrelevantes Implementierungsdetail. Sie könnten eine Implementierung haben, die keine Symbole interniert, und die Sprache wäre genau dieselbe.

Was ist eigentlich ein Symbol? Die Antwort liegt in etwas, das Julia und Lisp gemeinsam haben - der Fähigkeit, den Code der Sprache als Datenstruktur in der Sprache selbst darzustellen. Einige Leute nennen dies "Homoikonizität" ( Wikipedia ), andere scheinen nicht zu glauben, dass allein eine Sprache ausreicht, um homoikonisch zu sein. Aber die Terminologie spielt keine Rolle. Der Punkt ist, dass eine Sprache, wenn sie ihren eigenen Code darstellen kann, eine Möglichkeit benötigt, Dinge wie Zuweisungen, Funktionsaufrufe, Dinge, die als Literalwerte geschrieben werden können usw. darzustellen. Sie benötigt auch eine Möglichkeit, ihre eigenen Variablen darzustellen. Das heißt, Sie benötigen eine Möglichkeit, die Daten fooauf der linken Seite als Daten darzustellen :

foo == "foo"

Jetzt kommen wir zum Kern der Sache: Der Unterschied zwischen einem Symbol und einer Zeichenfolge ist der Unterschied zwischen fooauf der linken Seite dieses Vergleichs und "foo"auf der rechten Seite. Auf der linken Seite foobefindet sich ein Bezeichner, der den Wert auswertet, der fooim aktuellen Bereich an die Variable gebunden ist . Auf der rechten Seite "foo"befindet sich ein Zeichenfolgenliteral, das den Zeichenfolgenwert "foo" ergibt. Ein Symbol in Lisp und Julia ist, wie Sie eine Variable als Daten darstellen. Eine Zeichenfolge repräsentiert nur sich selbst. Sie können den Unterschied erkennen, indem Sie sich evalbei ihnen bewerben :

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

julia> foo = "hello"
"hello"

julia> eval(:foo)
"hello"

julia> eval("foo")
"foo"

Was das Symbol :fooergibt, hängt davon ab, an was - wenn überhaupt - die Variable foogebunden ist, während es "foo"immer nur "foo" ergibt . Wenn Sie in Julia Ausdrücke erstellen möchten, die Variablen verwenden, verwenden Sie Symbole (unabhängig davon, ob Sie sie kennen oder nicht). Beispielsweise:

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

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

Was das abgelegte Zeug zeigt, ist unter anderem, dass sich :fooinnerhalb des Ausdrucksobjekts ein Symbolobjekt befindet, das Sie durch Zitieren des Codes erhalten foo = "bar". Hier ist ein weiteres Beispiel für die Erstellung eines Ausdrucks mit dem :fooin der Variablen gespeicherten Symbol 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"

Wenn Sie dies versuchen, wenn symes an die Zeichenfolge gebunden ist, "foo"funktioniert es nicht:

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

Es ist ziemlich klar, warum dies nicht funktioniert - wenn Sie versucht haben, "foo" = "bar"von Hand zuzuweisen , funktioniert es auch nicht.

Dies ist die Essenz eines Symbols: Ein Symbol wird verwendet, um eine Variable in der Metaprogrammierung darzustellen. Sobald Sie Symbole als Datentyp haben, wird es natürlich verlockend, sie für andere Zwecke zu verwenden, beispielsweise als Hash-Schlüssel. Dies ist jedoch eine zufällige, opportunistische Verwendung eines Datentyps, der einen anderen Hauptzweck hat.

Beachten Sie, dass ich vor einiger Zeit aufgehört habe, über Ruby zu sprechen. Das liegt daran, dass Ruby nicht homoikonisch ist: Ruby repräsentiert seine Ausdrücke nicht als Ruby-Objekte. Rubys Symboltyp ist also eine Art Überrestorgan - eine übrig gebliebene Adaption, die von Lisp geerbt wurde, aber nicht mehr für den ursprünglichen Zweck verwendet wird. Ruby-Symbole wurden für andere Zwecke kooptiert - als Hash-Schlüssel, um Methoden aus Methodentabellen zu ziehen -, aber Symbole in Ruby werden nicht zur Darstellung von Variablen verwendet.

Der Grund, warum Symbole in DataFrames anstelle von Zeichenfolgen verwendet werden, liegt darin, dass in DataFrames häufig Muster verwendet werden, um Spaltenwerte an Variablen innerhalb von vom Benutzer bereitgestellten Ausdrücken zu binden. Daher sind Spaltennamen natürlich Symbole, da Symbole genau das sind, was Sie zur Darstellung von Variablen als Daten verwenden. Derzeit müssen Sie schreiben, df[:foo]um auf die fooSpalte zuzugreifen. In Zukunft können Sie jedoch möglicherweise stattdessen darauf zugreifen df.foo. Wenn dies möglich wird, können mit dieser praktischen Syntax nur auf Spalten zugegriffen werden, deren Namen gültige Bezeichner sind.

Siehe auch:

StefanKarpinski
quelle
6
Internierung: In der Informatik ist die Zeichenfolgeninternierung eine Methode, bei der nur eine Kopie jedes einzelnen Zeichenfolgenwerts gespeichert wird, der unveränderlich sein muss. Durch das Internieren von Zeichenfolgen werden einige Zeichenfolgenverarbeitungsaufgaben zeit- oder platzsparender, und es wird mehr Zeit benötigt, wenn die Zeichenfolge erstellt oder interniert wird. en.wikipedia.org/wiki/String_interning
Xiaodai
An einem Punkt schreibst du eval(:foo)und an einem anderen eval(sym). Gibt es einen bedeutenden Unterschied zwischen eval(:foo)und eval(foo)?
Graustufen
Sehr gerne: eval(:foo)Gibt einen Wert an, an den die Variable foogebunden ist, während eval(foo)Aufrufe diesen Wert aufrufen. Schreiben eval(:foo)entspricht nur foo(im globalen Bereich) , so eval(foo)ist wie eval(eval(:foo)).
StefanKarpinski