Почему у Haskell нет символов (a la ruby)/атомов (a la erlang)?

Двумя языками, на которых я использовал символы, являются Ruby и Erlang, и я всегда считал их чрезвычайно полезными.

Haskell имеет алгебраические типы данных, но я все же считаю, что символы были бы очень удобными. Непосредственное использование, которое приходит на ум, состоит в том, что, поскольку символы изоморфны целым числам, вы можете использовать их там, где вы бы использовали интегральный или строковый "первичный ключ".

Синтаксический сахар для атомов может быть незначительным -: что-то или что-то типа; является атомом. Все атомы являются экземплярами типа Atom, который выводит Show и Eq. Затем вы можете использовать его для более описательных кодов ошибок, например

type ErrorCode = Atom
type Message = String
data Error = Error ErrorCode Message
loginError = Error :redirect "Please login first"

В этом случае: перенаправление более эффективно, чем использование строки ( "перенаправление" ) и более понятное, чем целое число (404).

Преимущество может показаться незначительным, но я говорю, что стоит добавить атомы в качестве языковой функции (или, по крайней мере, расширения GHC).

Итак, почему символы не были добавлены в язык? Или я думаю об этом неправильно?

Ответ 1

Я согласен с ответом Camcann, что он, вероятно, отсутствует в основном потому, что его нужно будет испечь достаточно глубоко в реализации, и он слишком мало используется для этого уровня сложности. В Erlang (и Prolog и Lisp) символы (или атомы) обычно служат специальными маркерами и служат в основном тем же понятием, что и конструктор. В Lisp динамическая среда включает компилятор, поэтому она частично также (полезная) концепция компилятора протекает во время выполнения.

Проблема заключается в следующем: интерполяция символов нечиста (она изменяет таблицу символов). Однако, поскольку мы никогда не изменяем существующий объект, он, по сути, прозрачен, но если он реализован наивно, это может привести к утечке пространства во время выполнения. Фактически, как в настоящее время реализовано в Erlang, вы можете фактически свернуть виртуальную машину, интернируя слишком много символов/атомов (текущий предел составляет 2 ^ 20, я думаю), потому что они никогда не смогут собрать мусор. Это также сложно реализовать в параллельных настройках без огромной блокировки вокруг таблицы символов.

Однако обе проблемы могут быть (и были) решены. Например, см. Erlang EEP 20. Я использую эту технику в пакете simple-atom. Он использует unsafePerformIO под капотом, но только в (надеюсь) редких случаях. Он мог бы по-прежнему использовать некоторую помощь от GC для оптимизации, подобной сокращению направления. Он также использует довольно много IORef внутри, что не слишком велико для производительности и использования памяти.

Таким образом, это можно сделать, но его реализация является нетривиальной. Авторы компилятора всегда взвешивают мощь функции против ее усилий по внедрению и обслуживанию, и кажется, что первоклассные символы теряются на этом.

Ответ 2

Я думаю, что самый простой ответ состоит в том, что из символов Lisp -style (где и Ruby и Erlang получили эту идею, я считаю) используются для, в Haskell большинство из них либо:

  • Уже сделано другим способом - например. тип данных с кучей нулевых конструкторов, которые также ведут себя как "удобные имена для целых чисел".

  • Неловко вписываться - вещи, которые существуют на уровне синтаксиса языка вместо регулярных данных, обычно имеют больше информации о типе, связанную с ними, но символы должны либо быть разными типами друг от друга (почти бесполезно без какого-либо легкого типа ad-hoc sum) или все того же типа (в этом случае они едва ли отличаются от использования строк).

Кроме того, имейте в виду, что сам Haskell на самом деле очень, очень маленький язык. Очень мало "запекается", а из вещей, которые больше всего, являются просто синтаксическим сахаром для других примитивов. Это немного менее верно, если вы включаете кучу расширений GHC, но GHC с -XAndTheKitchenSinkToo не является тем же языком, что и Haskell.

Кроме того, Haskell очень поддается псевдо-синтаксису и метапрограммированию, поэтому вы можете многое сделать, даже если он не встроен. Особенно, если вы попадаете в TH и страшный тип метапрограммирования и что-то еще.

В основном это сводится к тому, что большая часть практической полезности символов уже доступна из других функций, а материал, который недоступен, будет сложнее добавить, чем это стоит.

Ответ 3

Атомы не предоставляются языком, но могут быть реализованы как библиотека:

http://hackage.haskell.org/package/simple-atom

Есть несколько других libs для хака, но этот выглядит самым последним и ухоженным.

Ответ 4

Haskell использует конструкторы типов * вместо символов, так что набор символов, которые может выполнять функция, закрывается и может быть обоснован системой типов. Вы могли бы добавить символы на язык, но это поставит вас в том же месте, что и при использовании строк - вам нужно будет проверить все возможные символы против нескольких с известными значениями во время выполнения, добавить обработку ошибок по всему месту и т.д. Это было бы очень сложным решением для проверки времени компиляции.

Основное различие между строками и символами - интернирование - символы являются атомарными и могут сравниваться в постоянное время. Оба являются типами с по существу бесконечным числом различных значений, хотя и против зерна Haskell, определяющего аргументы и результаты с конечными типами.

  • Я больше знаком с OCaml, чем с Haskell, поэтому "конструктор типов" может и не быть правильным термином. Такие вещи, как None или Just 3.

Ответ 5

Непосредственное использование, которое приходит на ум, состоит в том, что поскольку символы изоморфны целым числам, вы можете использовать их там, где вы бы использовали интегральный или строковый "первичный ключ".

Используйте Enum вместо этого.

data FileType = GZipped | BZipped | Plain
  deriving Enum

descr ft  =  ["compressed with gzip",
              "compressed with bzip2",
              "uncompressed"] !! fromEnum ft