¿Qué es un "símbolo" en Julia?

131

Específicamente: estoy tratando de usar el paquete DataFrames de Julia, específicamente la función readtable () con la opción de nombres, pero eso requiere un vector de símbolos.

  • ¿Qué es un símbolo?
  • ¿Por qué elegirían eso sobre un vector de cadenas?

Hasta ahora solo he encontrado un puñado de referencias a la palabra símbolo en el idioma Julia. Parece que los símbolos están representados por ": var", pero no está claro para mí cuáles son.

Aparte: puedo correr

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

Mis dos preguntas con viñetas siguen en pie.

Mageek
fuente
3
Puede encontrar alguna conversación sobre este tema aquí: groups.google.com/d/msg/julia-users/MS7KW8IU-0o/cQ-yDOs_CQEJ
jverzani

Respuestas:

231

Los símbolos en Julia son los mismos que en Lisp, Scheme o Ruby. Sin embargo, las respuestas a esas preguntas relacionadas no son realmente satisfactorias , en mi opinión. Si lee esas respuestas, parece que la razón por la cual un símbolo es diferente de una cadena es que las cadenas son mutables mientras que los símbolos son inmutables, y los símbolos también están "internados", sea lo que sea que eso signifique. Las cuerdas son mutables en Ruby y Lisp, pero no en Julia, y esa diferencia es en realidad una pista falsa. El hecho de que los símbolos estén internados, es decir, procesados ​​por la implementación del lenguaje para comparaciones rápidas de igualdad, también es un detalle de implementación irrelevante. Podría tener una implementación que no incluya símbolos internos y el lenguaje sería exactamente el mismo.

Entonces, ¿qué es realmente un símbolo? La respuesta radica en algo que Julia y Lisp tienen en común: la capacidad de representar el código del lenguaje como una estructura de datos en el propio lenguaje. Algunas personas llaman a esto "homoiconicidad" ( Wikipedia ), pero otros no parecen pensar que solo es suficiente para que un idioma sea homicónico. Pero la terminología realmente no importa. El punto es que cuando un lenguaje puede representar su propio código, necesita una forma de representar cosas como asignaciones, llamadas a funciones, cosas que pueden escribirse como valores literales, etc. También necesita una forma de representar sus propias variables. Es decir, necesita una forma de representar, como datos, el foolado izquierdo de esto:

foo == "foo"

Ahora estamos llegando al meollo del asunto: la diferencia entre un símbolo y una cadena es la diferencia entre fooel lado izquierdo de esa comparación y "foo"el lado derecho. A la izquierda, foohay un identificador y evalúa el valor vinculado a la variable fooen el ámbito actual. A la derecha, "foo"es un literal de cadena y se evalúa como el valor de cadena "foo". Un símbolo tanto en Lisp como en Julia es cómo se representa una variable como datos. Una cadena solo se representa a sí misma. Puede ver la diferencia aplicándoles eval:

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

julia> foo = "hello"
"hello"

julia> eval(:foo)
"hello"

julia> eval("foo")
"foo"

Lo que :fooevalúa el símbolo depende de a qué, en todo caso, fooestá vinculada la variable , mientras que "foo"siempre se evalúa como "foo". Si desea construir expresiones en Julia que usan variables, entonces está usando símbolos (lo sepa o no). Por ejemplo:

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

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

Lo que eso muestra, entre otras cosas, es que hay un :fooobjeto de símbolo dentro del objeto de expresión que obtienes citando el código foo = "bar". Aquí hay otro ejemplo, construyendo una expresión con el símbolo :fooalmacenado en 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 intenta hacer esto cuando symestá vinculado a la cadena "foo", no funcionará:

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 bastante claro ver por qué esto no funcionará: si trató de asignar "foo" = "bar"a mano, tampoco funcionará.

Esta es la esencia de un símbolo: un símbolo se utiliza para representar una variable en la metaprogramación. Una vez que tenga símbolos como tipo de datos, por supuesto, se vuelve tentador usarlos para otras cosas, como las teclas hash. Pero ese es un uso incidental y oportunista de un tipo de datos que tiene otro propósito principal.

Tenga en cuenta que dejé de hablar de Ruby hace un tiempo. Esto se debe a que Ruby no es homoicónico: Ruby no representa sus expresiones como objetos Ruby. Entonces, el tipo de símbolo de Ruby es una especie de órgano vestigial: una adaptación sobrante, heredada de Lisp, pero que ya no se usa para su propósito original. Los símbolos de Ruby se han cooptado para otros fines, como claves hash, para extraer métodos de las tablas de métodos, pero los símbolos en Ruby no se usan para representar variables.

En cuanto a por qué los símbolos se usan en DataFrames en lugar de cadenas, es porque es un patrón común en DataFrames vincular los valores de columna a las variables dentro de las expresiones proporcionadas por el usuario. Por lo tanto, es natural que los nombres de columna sean símbolos, ya que los símbolos son exactamente lo que usa para representar las variables como datos. Actualmente, tiene que escribir df[:foo]para acceder a la foocolumna, pero en el futuro, puede acceder a ella como en su df.foolugar. Cuando eso sea posible, solo las columnas cuyos nombres son identificadores válidos serán accesibles con esta conveniente sintaxis.

Ver también:

StefanKarpinski
fuente
66
Internación: en informática, la internación de cadenas es un método para almacenar solo una copia de cada valor de cadena distinto, que debe ser inmutable. Internar cadenas hace que algunas tareas de procesamiento de cadenas sean más eficientes en tiempo o espacio a costa de requerir más tiempo cuando la cadena se crea o se interna. en.wikipedia.org/wiki/String_interning
xiaodai
En un momento escribes eval(:foo)y en otro eval(sym). ¿Hay una diferencia significativa entre eval(:foo)y eval(foo)?
Escala de grises
Mucho: eval(:foo)da un valor al que fooestá vinculada la variable mientras que eval(foo)llama a evaluar ese valor. Escribir eval(:foo)es equivalente a just foo(en alcance global), así eval(foo)es como eval(eval(:foo)).
StefanKarpinski