Почему в Haskell отсутствуют "очевидные" классы типов
Рассмотрим объектно-ориентированные языки:
Большинство людей, имеющих опыт объектно-ориентированного программирования, знакомы с общими и интуитивно понятными интерфейсами на разных языках, которые отражают суть Java. Collection
& List
интерфейсы. Collection
относится к совокупности объектов, которые не обязательно имеют естественный порядок / индексацию. List
это коллекция, которая имеет естественный порядок / индексацию. Эти интерфейсы абстрагируют многие библиотечные структуры данных в Java, равно как и их эквивалентные интерфейсы в других языках, и для эффективной работы с большинством библиотечных структур данных требуется глубокое понимание этих интерфейсов.
Переход на Хаскелл:
Haskell имеет систему классов типов, которая действует на типы аналогично интерфейсам на объектах. У Haskell, кажется, есть хорошо разработанная иерархия классов типов в отношении Функторов, Применимых, Монад и т. Д., Когда тип рассматривает функциональность. Очевидно, они хотят правильных и хорошо абстрагированных классов типов. Тем не менее, когда вы смотрите на многие контейнеры Haskell (List
, Map
, Sequence
, Set
, Vector
они почти все имеют очень похожие (или идентичные) функции, но не абстрагируются через классы типов.
Некоторые примеры:
null
для проверки "пустоты"length
/size
для количества элементовelem
/member
для включения набораempty
и / илиsingleton
для строительства по умолчаниюunion
для набора союза(\\)
/diff
для заданной разницы(!)
/(!!)
для небезопасной индексации (частичная функция)(!?)
/lookup
для безопасного индексирования (общая функция)
Если я хочу использовать любую из перечисленных выше функций, но я импортировал два или более контейнеров, я должен начать скрывать функции от импортированных модулей или явно импортировать только необходимые функции из модулей или квалифицировать импортированные модули. Но поскольку все функции обеспечивают одинаковую логическую функциональность, это просто кажется хлопотным. Если функции были определены из классов типов, а не отдельно в каждом модуле, механика вывода типов компилятора могла бы решить эту проблему. Это также сделало бы переключение базовых контейнеров простым, если они разделяют классы типов (то есть: давайте просто использовать Sequence
вместо List
для лучшей эффективности произвольного доступа).
Почему у Хаскелла нет Collection
и / или Indexable
Тип-класс (ы), чтобы объединить и обобщить некоторые из этих функций?
7 ответов
Частично, причина в том, что монады и стрелы являются новыми, инновационными функциями Haskell, в то время как коллекции являются относительно более приземленными. Haskell имеет долгую историю как язык исследований; Интересные вопросы исследования (проектирование экземпляров монад и определение общих операций для монад) требуют больше усилий по разработке, чем полировка "промышленного уровня" (определение API-интерфейсов контейнеров).
Частично, причина в том, что эти типы происходят из трех разных пакетов (база, контейнеры и вектор), с тремя отдельными историями и дизайнерами. Это мешает их дизайнерам координировать предоставление экземпляров любого класса одного типа.
Частично, причина в том, что определить класс одного типа для всех пяти упомянутых вами контейнеров действительно сложно. List, Sequence и Vector относительно похожи, но Map и Set имеют совершенно разные ограничения. Для List, Sequence и Vector вам нужен простой класс конструктора, но для Set он не будет работать, поскольку Set требует экземпляра Ord для типа элемента. Хуже того, Map может поддерживать большинство ваших методов, но для ее одноэлементной функции нужны два параметра, а для остальных - только один.
Как указывали другие ответы, Haskell имеет тенденцию использовать другой словарный запас. Тем не менее, я не думаю, что они объяснили причину разницы очень хорошо.
В языке, подобном Java, функции не являются "гражданами первого класса"; Это правда, что анонимные функции доступны в последних версиях, но этот стиль интерфейса (Collection, Indexable, Interable и т. д.) был разработан до этого.
Это приводит к утомительной передаче нашего кода, поэтому мы предпочитаем , чтобы данные других людей передавались в наш код. Например:
- Данные, реализующие Java
Iterable
давайте напишемfor (Foo x : anIterable) { ... }
- Данные, реализующие PHP
ArrayAccess
давайте напишемanArrayAccess[anIndex]
Этот стиль также можно увидеть в ОО-языках, которые реализуют генераторы, так как это еще один способ для нас написать for yieldedElement in aGenerator: ...
,
Haskell использует другой подход к своим классам типов: мы предпочитаем, чтобы наш код передавался другим людям. Несколько (упрощенных) примеров:
Functor
Мы принимаем наш код и применяем его к любым элементам, которые они содержатMonad
Примите наш код и примените его в некоторой "последовательности"Foldable
Мы принимаем наш код и используем его для "сокращения" своего содержания.
Java нужна только Iterable
так как мы должны вызвать наш код в нашем for
цикл, поэтому мы можем убедиться, что он вызывается правильно. Для Haskell требуются более конкретные классы типов, поскольку чужой код будет вызывать наш, поэтому нам нужно указать, как он должен вызываться; это map
, fold
, unfold
, так далее.?
К счастью, система типов помогает нам выбрать правильный метод;)
lens
Пакет предоставляет некоторые из этого.
Тестирование на пустоту, создание пустых контейнеров.
AsEmpty
класс типов изControl.Lens.Empty
,Доступ к элементам по ключу / индексу.
At
а такжеIxed
классы отControl.Lens.At
,Проверка на членство в set-like контейнерах.
Contains
класс типов изControl.Lens.At
,Добавление и удаление элементов в контейнерах, похожих на последовательности.
Cons
а такжеSnoc
классы отControl.Lens.Cons
,
Так же pure
метод Applicative
Класс типов часто можно использовать для создания "синглтонных" контейнеров. Для вещей, которые не являются функторами / аппликативами в Haskell, например Set
возможно point
от Data.Pointed
может быть использован.
В Haskell есть несколько классов типов для работы с коллекциями в базовом пакете: Functor, Foldable и Traversable могут быть полезны для работы с коллекциями, а классы типов Monoid, Applicative и / или Alternative могут быть полезны для конструирования коллекций.
Вместе эти классы охватывают большинство операций, упомянутых в вопросе, но, возможно, менее эффективны, чем более специфичные для контейнера функции (хотя многие из них являются методами классов, определения которых по умолчанию могут быть переопределены при необходимости).
null
для проверки "пустоты"
Складные подставки null
с базы 4.8 (any (const True)
является альтернативой для более ранних версий).
длина / размер для количества элементов:
Складные подставки length
с базы 4.8 (getSum . foldMap (const 1)
является альтернативой для более ранних версий).
элемент / член для включения набора
Складные подставки elem
, notElem
а также member
,
пусто и / или одиночно для конструкции по умолчанию
Для пустых есть mempty
из моноида и empty
из альтернативы. Для синглтона есть pure
от Applicative.
союз для множества союз
Есть mappend
из моноида и <|>
из альтернативы. Они не обязательно реализуют объединение множеств, но они реализуют некоторую форму объединения, которая хорошо работает вместе с empty и обычно также с singleton и find.
(\) / diff для заданной разности
К сожалению, этот не поддерживается.
(!) / (!!) для небезопасной индексации (частичная функция)
Вы могли бы использовать fromJust
вместе с функцией безопасной индексации.
(!?) / поиск безопасного индексирования (общая функция)
Есть find
от складной
Такие классы типов существуют в стандартном Haskell, но они не имеют того же имени, что и их эквивалентные OO-аналоги. Collection
Класс типов, например, называется Foldable
в Хаскеле. Вы можете использовать его для проверки, если структура пуста (foldr (const False) True x
) или подсчитать количество элементов (foldMap (const 1) x
) или для проверки на членство в наборе (foldr (\e' present -> (e==e') || present) False x
для некоторых e
).
Для таких операций, как поиск элементов, у вас есть Array
класс типов, который может работать для последовательных данных. Для большей гибкости вы можете написать свой собственный Indexable
класс, как это, например, (остерегайтесь линз):
class Indexable m k a where
at :: k -> Lens' m (Maybe a)
Нулевой элемент и множественное объединение принадлежат Monoid
класс типов (где mappend == union
). В этом свете разность множеств также может быть реализована в своем собственном классе типов Differentiable
(который, я уверен, уже существует в десятках библиотек Haskell), и у нас была бы полная совместимость с императивными языками.
Haskell, благодаря тому, что его разработали математики и тому подобное, не использует тот же словарь, что и большинство других языков, но будьте уверены, это не значит, что это не практичный язык в дополнение к тому, что он классный:-)
Законы. Хороший класс типов имеет законы. Большой класс типов обладает достаточной параметричностью, поэтому его законы являются "бесплатными теоремами". Класс типов без законов - это просто временная перегрузка имени.
Кроме того, проверьте classy-prelude и Edison-API.
У вас есть классы типов для разных аспектов сбора:
состав: моноид (модуль Data.Monoid)
последовательное управление: Applicative, Monad (модули Control.Applicative, Control.Monad)
последовательная композиция: Alternative, MonadPlus (модули Control.Applicative, Control.Monad)
непоследовательное отображение и редукция: Functor (мод. Data.Functor), Foldable (мод. Data.Foldable)
последовательное отображение и сокращение: Traversable (модуль Data.Traversable)
сериализация: двоичная (мод. Data.Binary)
сравнение: Eq, Ord (мод. Data.Eq, Data.Ord)
Текстуализация: Показать, Читать
глубокая оценка (в нормальной форме): NFData (мод. Control.DeepSeq)
универсальная проходимость типов данных: данные (мод. данные. данные)
За исключением того, что мономорфные коллекции (ByteString, IntSet, Text) не могут реализовать Functor и Foldable (для них требуется тип arity == 1 (Kind: * -> *))
Также ни (Set a) не реализует Functor.
Пакет mono-traversable переопределяет некоторые классы без исключения мономорфных типов.
Обновление Существует попытка поместить большинство функций в классы типов с помощью пакетов mono-traversable и classy-prelude.