Haskell "коллекции" языковой дизайн - PullRequest
20 голосов
/ 15 ноября 2010

Почему реализация Haskell так сфокусирована на связанных списках?

Например, я знаю, что Data.Sequence более эффективен для большинства операций со списками (кроме операции cons) и используетсямного;хотя синтаксически это "вряд ли поддерживается".Haskell приложил много усилий к функциональным абстракциям, таким как Functor и класс Foldable, но их синтаксис не совместим с синтаксисом списка по умолчанию.

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

Так что я думаю, что мое удивление может быть конкретизировано в таких вопросах, как:

  1. Почему тип map не равен (Functor f) => (a -> b) -> f a -> f b?
  2. Почему нельзя использовать функции [] и (:), например, длятип в Data.Sequence?

Я действительно надеюсь, что есть какое-то объяснение этому, которое не включает слова «обратная совместимость» или «он просто вырос таким образом», хотя, если выдумаю, что нет, пожалуйста, дайте мне знать.Любые соответствующие языковые расширения также приветствуются.

Ответы [ 5 ]

21 голосов
/ 15 ноября 2010

Перед тем как понять, вот краткое изложение проблемы и что вы можете с этим сделать. Конструкторы [] и (:) зарезервированы для списков и не могут быть переопределены. Если вы планируете использовать один и тот же код с несколькими типами данных, определите или выберите класс типов, представляющий интерфейс, который вы хотите поддерживать, и используйте методы из этого класса. Вот некоторые обобщенные функции, которые работают как со списками, так и с последовательностями. Я не знаю обобщения (:), но вы можете написать свой собственный.

  • fmap вместо map
  • mempty вместо []
  • mappend вместо (++)

Если вы планируете сделать одноразовую замену типа данных, то вы можете определить свои собственные имена для вещей и переопределить их позже.

-- For now, use lists
type List a = [a]
nil = []
cons x xs = x : xs

{- Switch to Seq in the future
-- type List a = Seq a
-- nil = empty
-- cons x xs = x <| xs
-}

Обратите внимание, что [] и (:) являются конструкторами: вы также можете использовать их для сопоставления с образцом. Сопоставление с образцом относится только к одному конструктору типов, поэтому вы не можете расширить шаблон для работы с новым типом данных, не переписав код сопоставления с образцом.


Почему в Haskell так много вещей, относящихся к списку

Списки обычно используются для представления последовательных вычислений , а не данных. В императивном языке вы можете создать Set с циклом, который создает элементы и вставляет их в набор один за другим. В Haskell вы делаете то же самое, создавая список, а затем передавая список в Set.fromList. Поскольку списки так близко соответствуют этой абстракции вычислений, у них есть место, которое вряд ли когда-либо заменит другая структура данных.

Факт остается фактом, что некоторые функции являются специфичными для списка, когда они могли бы быть общими. Некоторые общие функции, такие как map, были сделаны специфичными для списка, чтобы новым пользователям было меньше чему учиться. В частности, они предоставляют более простые и (было решено) более понятные сообщения об ошибках. Поскольку вместо этого можно использовать универсальные функции, проблема на самом деле просто в синтаксическом неудобстве. Стоит отметить, что реализации на языке Haskell имеют очень мало специального кода, поэтому новые структуры данных и методы могут быть такими же эффективными, как и "встроенные".

Есть несколько классов, которые являются полезными обобщениями списков:

  • Функтор поставляет fmap, обобщение map.
  • Monoid предоставляет методы, полезные для коллекций со спискообразной структурой. Пустой список [] обобщается для других контейнеров mempty, а конкатенация списка (++) обобщается для других контейнеров mappend.
  • Аппликативные и Monad предоставляют методы, которые полезны для интерпретации коллекций как вычислений.
  • Traversable и Foldable предоставляют полезные методы для выполнения вычислений над коллекциями.

Из них только Functor и Monad были в влиятельной спецификации Haskell 98, поэтому другие авторы библиотеки в той или иной степени упускали из виду, в зависимости от того, когда библиотека была написана и насколько активно она поддерживалась. Основные библиотеки хорошо поддерживали новые интерфейсы.

7 голосов
/ 15 ноября 2010

Я помню, как читал где-то, что map для списков по умолчанию, поскольку новички в Haskell будут откладываться, если они допустят ошибку и увидят сложную ошибку о «Функторах», о которой они понятия не имеют.Следовательно, они имеют map и fmap вместо просто map.

РЕДАКТИРОВАТЬ: Это "где-то" это Читатель Монады Выпуск 13, стр. 20, сноска 3:

3Вы можете спросить, зачем нам нужна отдельная функция карты.Почему бы просто не покончить с текущей функцией отображения списка и вместо этого переименовать fmap в map?Ну, это хороший вопрос.Обычный аргумент состоит в том, что кто-то, только изучающий Haskell, при неправильном использовании map, скорее увидит ошибку в списках, чем в Functors.

Для (:) функция (<|) выглядит какзамена.Я понятия не имею о [].

5 голосов
/ 15 ноября 2010

Придирка Data.Sequence не более эффективна для «операций со списками», она более эффективна для операций с последовательностями. Тем не менее, многие функции в Data.List действительно являются последовательными операциями. Дерево пальца внутри Data.Sequence должно выполнить немного больше работы для cons (<|), эквивалентного list (:), и его представление в памяти также несколько больше, чем список, так как оно сделано из двух типов данных FingerTree и глубокий. </p>

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

Список не является идеальным представлением для строк - структура памяти неэффективна, так как каждый Char обернут конструктором. Вот почему были представлены ByteStrings. Хотя они представлены в виде массива, ByteStrings должны выполнить небольшую административную работу - [Char] может быть конкурентоспособным, если вы используете короткие строки. В GHC есть языковые расширения для придания ByteStrings более строкового синтаксиса.

Другой основной ленивый функционал Clean всегда представлял строки как байтовые массивы, но его система типов сделала это более практичным - я считаю, что библиотека ByteString использует unsafePerfomIO под капотом.

4 голосов
/ 21 апреля 2014

С версией 7.8 ghc поддерживает литералы списка перегрузки, сравните руководство . Например, учитывая соответствующие IsList экземпляры, вы можете написать

['0' .. '9']             :: Set Char
[1 .. 10]                :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z']             :: Text

(цитата из документации).

2 голосов
/ 15 ноября 2010

Я почти уверен, что это не будет ответом на ваш вопрос, но все же.

Хотелось бы, чтобы у Haskell были более либеральные имена функций (mixfix!) a la Agda Тогда синтаксис для конструкторов списков (:, []) не был бы magic ; что позволяет нам по крайней мере скрыть тип списка и использовать те же токены для наших собственных типов.

Количество изменений кода при миграции между списком и пользовательскими типами последовательностей будет минимальным.

О map, вам немного повезло. Вы всегда можете скрыть карту и установить ее равной fmap самостоятельно.

import Prelude hiding(map)

map :: (Functor f) => (a -> b) -> f a -> f b
map = fmap

Прелюдия великолепна, но она не лучшая часть Хаскелла.

...