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