O que é um "símbolo" em Julia?

131

Especificamente: estou tentando usar o pacote DataFrames de Julia, especificamente a função readtable () com a opção de nomes, mas isso requer um vetor de símbolos.

  • o que é um símbolo?
  • por que eles escolheriam isso ao invés de um vetor de strings?

Até agora, encontrei apenas algumas referências à palavra símbolo na língua Julia. Parece que os símbolos são representados por ": var", mas não está claro para mim o que são.

Além: eu posso correr

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

Minhas duas perguntas com marcadores ainda permanecem.

Mageek
fonte
3
Alguma conversa sobre esse tópico pode ser encontrada aqui: groups.google.com/d/msg/julia-users/MS7KW8IU-0o/cQ-yDOs_CQEJ
jverzani

Respostas:

231

Os símbolos em Julia são os mesmos que em Lisp, Scheme ou Ruby. No entanto, na minha opinião , as respostas a essas perguntas relacionadas não são realmente satisfatórias . Se você ler essas respostas, parece que a razão pela qual um símbolo é diferente de uma string é que as strings são mutáveis, enquanto os símbolos são imutáveis ​​e os símbolos também são "internados" - o que quer que isso signifique. As strings são mutáveis ​​em Ruby e Lisp, mas elas não estão em Julia, e essa diferença é realmente um arenque vermelho. O fato de os símbolos serem internados - isto é, hash pela implementação da linguagem para comparações rápidas de igualdade - também é um detalhe irrelevante da implementação. Você pode ter uma implementação que não armazene símbolos e o idioma seria exatamente o mesmo.

Então, o que é realmente um símbolo? A resposta está em algo que Julia e Lisp têm em comum - a capacidade de representar o código da linguagem como uma estrutura de dados na própria linguagem. Algumas pessoas chamam isso de "homoiconicidade" ( Wikipedia ), mas outras não parecem pensar que isso seja suficiente para que um idioma seja homoicônico. Mas a terminologia realmente não importa. O ponto é que, quando uma linguagem pode representar seu próprio código, ela precisa de uma maneira de representar coisas como atribuições, chamadas de função, coisas que podem ser escritas como valores literais etc. Ela também precisa de uma maneira de representar suas próprias variáveis. Ou seja, você precisa de uma maneira de representar - como dados - o foolado esquerdo disso:

foo == "foo"

Agora estamos chegando ao cerne da questão: a diferença entre um símbolo e uma string é a diferença entre fooo lado esquerdo dessa comparação e "foo"o lado direito. À esquerda, foohá um identificador e ele avalia o valor associado à variável foono escopo atual. À direita, "foo"é uma string literal e avalia o valor da string "foo". Um símbolo no Lisp e na Julia é como você representa uma variável como dados. Uma string apenas representa a si mesma. Você pode ver a diferença aplicando evala eles:

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

julia> foo = "hello"
"hello"

julia> eval(:foo)
"hello"

julia> eval("foo")
"foo"

O que o símbolo :fooavalia depende de qual - ou alguma coisa - a variável fooestá vinculada, enquanto "foo"sempre apenas avalia como "foo". Se você deseja construir expressões em Julia que usam variáveis, então você está usando símbolos (se você conhece ou não). Por exemplo:

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

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

O que esse material despejado mostra, entre outras coisas, é que há um :fooobjeto de símbolo dentro do objeto de expressão que você obtém citando o código foo = "bar". Aqui está outro exemplo, construindo uma expressão com o símbolo :fooarmazenado na variável 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"

Se você tentar fazer isso quando symestiver vinculado à string "foo", não 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""

É bem claro para ver por que isso não funcionará - se você tentou atribuir "foo" = "bar"manualmente, também não funcionará.

Esta é a essência de um símbolo: um símbolo é usado para representar uma variável na metaprogramação. Depois de ter símbolos como um tipo de dados, é claro, torna-se tentador usá-los para outras coisas, como chaves de hash. Mas esse é um uso casual e oportunista de um tipo de dados que tem outro objetivo principal.

Note que eu parei de falar sobre Ruby há um tempo. Isso ocorre porque Ruby não é homoicônico: Ruby não representa suas expressões como objetos Ruby. Portanto, o tipo de símbolo de Ruby é uma espécie de órgão vestigial - uma adaptação restante, herdada de Lisp, mas não mais usada para seu propósito original. Os símbolos Ruby foram cooptados para outros fins - como chaves de hash, para extrair métodos das tabelas de métodos - mas os símbolos no Ruby não são usados ​​para representar variáveis.

Quanto ao motivo pelo qual os símbolos são usados ​​nos DataFrames, e não nas cadeias, é porque é um padrão comum nos DataFrames vincular valores de colunas a variáveis ​​dentro de expressões fornecidas pelo usuário. Portanto, é natural que os nomes das colunas sejam símbolos, pois os símbolos são exatamente o que você usa para representar variáveis ​​como dados. Atualmente, você precisa escrever df[:foo]para acessar a foocoluna, mas, no futuro, poderá acessá-la como df.fooalternativa. Quando isso se tornar possível, apenas as colunas cujos nomes são identificadores válidos estarão acessíveis com esta sintaxe conveniente.

Veja também:

StefanKarpinski
fonte
6
Internação: Na ciência da computação, a internação por string é um método de armazenar apenas uma cópia de cada valor distinto da string, que deve ser imutável. As cadeias internas tornam algumas tarefas de processamento de cadeias mais eficientes em termos de tempo ou espaço, exigindo mais tempo quando a cadeia é criada ou internada. pt.wikipedia.org/wiki/String_interning
xiaodai
Em um ponto você escreve eval(:foo)e em outro eval(sym). Existe uma diferença significativa entre eval(:foo)e eval(foo)?
Escala de cinza
Muito: eval(:foo)dá valor ao qual a variável fooestá vinculada, ao passo que eval(foo)chama eval nesse valor. Escrever eval(:foo)é equivalente a apenas foo(no escopo global) assim eval(foo)é eval(eval(:foo)).
StefanKarpinski