Что такое «символ» у Юлии?

133

В частности: я пытаюсь использовать пакет Julia DataFrames, в частности функцию readtable () с параметром names, но для этого требуется вектор символов.

  • что такое символ?
  • почему они выбрали это вместо вектора строк?

Пока что я нашел лишь несколько ссылок на слово «символ» в языке Julia. Кажется, что символы представлены как ": var", но мне далеко не ясно, что это такое.

В сторону: я могу бежать

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

Два моих вопроса, отмеченных списком, все еще в силе

Mageek
источник
3
Некоторые разговоры на эту тему можно найти здесь: groups.google.com/d/msg/julia-users/MS7KW8IU-0o/cQ-yDOs_CQEJ
jverzani

Ответы:

233

Символы в Julia такие же, как в Lisp, Scheme или Ruby. Однако, на мой взгляд , ответы на эти вопросы не совсем удовлетворительны . Если вы читаете эти ответы, кажется, что причина, по которой символ отличается от строки, заключается в том, что строки являются изменяемыми, в то время как символы неизменны, и символы также «интернированы» - что бы это ни значило. Строки действительно могут быть изменяемыми в Ruby и Lisp, но их нет в Julia, и это различие на самом деле отвлекает. Тот факт, что символы интернированы, то есть хешируются реализацией языка для быстрого сравнения на равенство, также является несущественной деталью реализации. У вас может быть реализация, в которой не используются внутренние символы, и язык будет точно таким же.

Так что же такое символ на самом деле? Ответ кроется в том, что у Julia и Lisp общее - в способности представлять код языка как структуру данных на самом языке. Некоторые люди называют это «гомоиконностью» ( Википедия ), но другие, похоже, не думают, что одного этого достаточно для того, чтобы язык был гомоиконным. Но терминология особого значения не имеет. Дело в том, что когда язык может представлять свой собственный код, ему нужен способ представления таких вещей, как присваивания, вызовы функций, вещи, которые могут быть записаны как буквальные значения и т. Д. Ему также нужен способ представления собственных переменных. То есть вам нужен способ представить - как данные - fooлевую часть этого:

foo == "foo"

Теперь мы подходим к сути вопроса: разница между символом и строкой - это разница между fooлевой частью этого сравнения и "foo"правой частью. Слева foo- идентификатор, который оценивает значение, привязанное к переменной fooв текущей области. Справа "foo"- строковый литерал, оценивающий строковое значение «foo». Символ как в Lisp, так и в Julia - это то, как вы представляете переменную как данные. Строка просто представляет себя. Вы можете увидеть разницу, применив evalк ним:

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

julia> foo = "hello"
"hello"

julia> eval(:foo)
"hello"

julia> eval("foo")
"foo"

То, что символ :fooоценивает, зависит от того, к чему - если вообще что-либо - переменная fooпривязана, тогда как "foo"всегда просто оценивается как "foo". Если вы хотите создавать выражения в Julia, которые используют переменные, вы используете символы (знаете вы об этом или нет). Например:

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

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

То, что вываливали, показывает, среди прочего, что есть :fooобъект символа внутри объекта выражения, который вы получаете, цитируя код foo = "bar". Вот еще один пример построения выражения с символом, :fooхранящимся в переменной 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"

Если вы попытаетесь сделать это, когда symпривязан к строке "foo", это не сработает:

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

Понятно, почему это не сработает - если вы попытались назначить "foo" = "bar"вручную, это тоже не сработает.

В этом суть символа: символ используется для представления переменной в метапрограммировании. Если у вас есть символы в качестве типа данных, конечно, возникает соблазн использовать их для других вещей, например, в качестве хеш-ключей. Но это случайное, оппортунистическое использование типа данных, имеющего другую основную цель.

Обратите внимание, что я перестал говорить о Ruby некоторое время назад. Это потому, что Ruby не гомоиконичен: Ruby не представляет свои выражения как объекты Ruby. Итак, тип символа Ruby - это своего рода рудиментарный орган - оставшаяся адаптация, унаследованная от Lisp, но больше не используемая по своему первоначальному назначению. Символы Ruby были использованы для других целей - в качестве ключей хеширования для извлечения методов из таблиц методов - но символы в Ruby не используются для представления переменных.

Что касается того, почему символы используются в DataFrames, а не в строках, это потому, что это распространенный шаблон в DataFrames для привязки значений столбцов к переменным внутри предоставленных пользователем выражений. Поэтому естественно, чтобы имена столбцов были символами, поскольку символы - это именно то, что вы используете для представления переменных в виде данных. В настоящее время df[:foo]для доступа к fooстолбцу необходимо писать , но в будущем вы можете получить к нему доступ как df.fooвместо этого. Когда это станет возможным, с этим удобным синтаксисом будут доступны только столбцы, имена которых являются допустимыми идентификаторами.

Смотрите также:

StefanKarpinski
источник
6
Интернирование: в информатике интернирование строк - это метод хранения только одной копии каждого отдельного строкового значения, которое должно быть неизменным. Интернирование строк делает некоторые задачи обработки строк более эффективными по времени или пространству за счет того, что для создания или интернирования строки требуется больше времени. en.wikipedia.org/wiki/String_interning
сяодай,
В один момент вы пишете, eval(:foo)а в другой eval(sym). Есть ли значимая разница между eval(:foo)и eval(foo)?
Оттенки серого
1
Очень похоже: eval(:foo)дает значение, к которому fooпривязана переменная , тогда как eval(foo)вызывает eval для этого значения. Написание eval(:foo)эквивалентно просто foo(в глобальном масштабе) так eval(foo)похоже eval(eval(:foo)).
Стефан Карпински