Co to jest „symbol” w Julii?

131

W szczególności: próbuję użyć pakietu DataFrames Julii, a konkretnie funkcji readtable () z opcją names, ale wymaga to wektora symboli.

  • co to jest symbol?
  • dlaczego mieliby wybrać to zamiast wektora ciągów?

Jak dotąd znalazłem tylko kilka odniesień do słowa symbol w języku Julia. Wydaje się, że symbole są reprezentowane przez „: var”, ale nie jest dla mnie jasne, czym one są.

Na bok: mogę biec

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

Moje dwa pytania z punktorami wciąż są aktualne.

Mageek
źródło
3
Niektóre rozmowy na ten temat można znaleźć tutaj: groups.google.com/d/msg/julia-users/MS7KW8IU-0o/cQ-yDOs_CQEJ
jverzani

Odpowiedzi:

231

Symbole w Julii są takie same jak w Lisp, Scheme czy Ruby. Jednak moim zdaniem odpowiedzi na te pytania nie są do końca satysfakcjonujące . Jeśli przeczytasz te odpowiedzi, wydaje się, że powodem, dla którego symbol różni się od łańcucha, jest to, że łańcuchy są zmienne, podczas gdy symbole są niezmienne, a symbole są również „internowane” - cokolwiek to znaczy. Tak się składa, że ​​ciągi znaków są zmienne w Rubim i Lispie, ale nie ma ich w Julii, a ta różnica to tak naprawdę czerwony śledź. Fakt, że symbole są internowane - tj. Haszowane przez implementację języka w celu szybkiego porównania równości - jest również nieistotnym szczegółem implementacji. Możesz mieć implementację, która nie internuje symboli, a język byłby dokładnie taki sam.

Więc czym tak naprawdę jest symbol? Odpowiedź tkwi w czymś, co łączy Julię i Lisp - umiejętność reprezentowania kodu języka jako struktury danych w samym języku. Niektórzy nazywają to „homoikonicznością” ( Wikipedia ), ale inni nie uważają, że sam język wystarczy, aby język był homoikoniczny. Ale terminologia tak naprawdę nie ma znaczenia. Chodzi o to, że jeśli język może reprezentować swój własny kod, potrzebuje sposobu reprezentowania rzeczy, takich jak przypisania, wywołania funkcji, rzeczy, które można zapisać jako wartości dosłowne itp. Potrzebny jest również sposób reprezentowania własnych zmiennych. Oznacza to, że potrzebujesz sposobu, aby przedstawić - jako dane - foopo lewej stronie tego:

foo == "foo"

Teraz dochodzimy do sedna sprawy: różnica między symbolem a łańcuchem to różnica między foopo lewej stronie tego porównania a "foo"po prawej stronie. Po lewej stronie fooznajduje się identyfikator, którego wynikiem jest wartość powiązana ze zmienną foow bieżącym zakresie. Po prawej stronie "foo"znajduje się literał łańcuchowy, którego wynikiem jest wartość ciągu „foo”. Symbol w Lisp i Julia to sposób, w jaki reprezentujesz zmienną jako dane. Ciąg po prostu reprezentuje siebie. Możesz zobaczyć różnicę, stosując się evaldo nich:

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

julia> foo = "hello"
"hello"

julia> eval(:foo)
"hello"

julia> eval("foo")
"foo"

To, do czego :fooszacowany jest symbol, zależy od tego, do czego - jeśli w ogóle - foojest przypisana zmienna , podczas gdy "foo"zawsze wylicza się jako „foo”. Jeśli chcesz konstruować wyrażenia w Julii, które używają zmiennych, to używasz symboli (czy o tym wiesz, czy nie). Na przykład:

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

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

To, co wyrzucone rzeczy, pokazuje, między innymi, to, że :foowewnątrz obiektu wyrażenia znajduje się obiekt symbolu, który uzyskuje się poprzez cytowanie kodu foo = "bar". Oto kolejny przykład konstruowania wyrażenia z symbolem :fooprzechowywanym w zmiennej 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"

Jeśli spróbujesz to zrobić, gdy symjest powiązany z łańcuchem "foo", to nie zadziała:

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

Jest całkiem jasne, dlaczego to nie zadziała - jeśli spróbujesz przypisać "foo" = "bar"ręcznie, również nie zadziała.

To jest istota symbolu: symbol jest używany do reprezentowania zmiennej w metaprogramowaniu. Kiedy już masz symbole jako typ danych, kuszące staje się użycie ich do innych celów, takich jak klucze mieszające. Ale jest to przypadkowe, oportunistyczne użycie typu danych, które ma inny główny cel.

Zwróć uwagę, że jakiś czas temu przestałem mówić o Rubim. Dzieje się tak, ponieważ Ruby nie jest homoiconic: Ruby nie reprezentuje swoich wyrażeń jako obiektów Rubiego. Tak więc typ symbolu Ruby jest rodzajem szczątkowego organu - pozostałej adaptacji, odziedziczonej po Lispie, ale nie jest już używana do swojego pierwotnego celu. Symbole Rubiego zostały dokooptowane do innych celów - jako klucze haszujące, aby wyciągnąć metody z tabel metod - ale symbole w Rubim nie są używane do reprezentowania zmiennych.

Jeśli chodzi o to, dlaczego symbole są używane w DataFrames, a nie w łańcuchach, to dlatego, że jest to powszechny wzorzec w DataFrames, który wiąże wartości kolumn ze zmiennymi wewnątrz wyrażeń dostarczonych przez użytkownika. Dlatego naturalne jest, że nazwy kolumn są symbolami, ponieważ symbole są dokładnie tym, czego używasz do przedstawiania zmiennych jako danych. Obecnie musisz pisać, df[:foo]aby uzyskać dostęp do fookolumny, ale w przyszłości możesz mieć do niej dostęp jako df.foozamiast tego. Kiedy stanie się to możliwe, tylko kolumny, których nazwy są prawidłowymi identyfikatorami, będą dostępne przy użyciu tej wygodnej składni.

Zobacz też:

Stefan Karpiński
źródło
6
Internowanie: W informatyce internowanie ciągów jest metodą przechowywania tylko jednej kopii każdej odrębnej wartości ciągu, która musi być niezmienna. Internowanie ciągów sprawia, że ​​niektóre zadania przetwarzania ciągów są bardziej wydajne pod względem czasu lub miejsca, kosztem dłuższego czasu na utworzenie lub umieszczenie ciągu. en.wikipedia.org/wiki/String_interning
xiaodai
W pewnym momencie piszesz, eval(:foo)aw innym eval(sym). Czy istnieje znacząca różnica między eval(:foo)i eval(foo)?
Skala szarości
Bardzo dobrze: eval(:foo)podaje wartość, do której foojest przypisana zmienna, podczas gdy eval(foo)wywołuje eval na tej wartości. Pisanie eval(:foo)jest równoważne z just foo(w zakresie globalnym), więc eval(foo)jest jak eval(eval(:foo)).
Stefan Karpiński