Почему Haskell отсутствует "очевидный" Typeclasses

Рассмотрим объектно-ориентированные языки:

Большинство людей, исходящих из объектно-ориентированного программирования, знакомы с общими и интуитивно понятными интерфейсами на разных языках, которые захватывают суть Java Collection и List. Collection относится к коллекции объектов, которая не обязательно имеет естественный порядок/индексацию. A List представляет собой набор, который имеет естественное упорядочение/индексацию. Эти интерфейсы абстрагируют многие библиотечные структуры данных в Java, как и их эквивалентные интерфейсы на других языках, и для эффективного взаимодействия с большинством библиотечных структур данных требуется глубокое понимание этих интерфейсов.

Переход к Haskell:

Haskell имеет систему типа-типа, которая действует на типы аналогично интерфейсам на объектах. Кажется, что у Haskell есть хорошо спроектированная иерархия классов типов в отношении Функторов, Аппликативных, Монад и т.д., Когда они относятся к функциям типа. Очевидно, они хотят правильные и хорошо абстрагированные типы классов. Однако, когда вы смотрите на многие контейнеры Haskell ( List, Map, Sequence, Set, Vector), они почти все имеют очень похожие (или идентичные) функции, но не абстрагируется через классы типов.

Некоторые примеры:

  • null для проверки "пустоты"
  • length / size для подсчета элементов
  • elem / member для включения включения
  • empty и/или singleton для построения по умолчанию
  • union для объединения set
  • (\\) / diff для заданной разницы
  • (!) / (!!) для небезопасной индексации (частичная функция)
  • (!?) / lookup для безопасной индексации (общая функция)

Если я хочу использовать любую из вышеперечисленных функций, но я импортировал два или более контейнера, я должен начать скрывать функции из импортированных модулей или явно импортировать только необходимые функции из модулей или квалифицировать импортированные модули. Но так как все функции обеспечивают одну и ту же логическую функциональность, это просто похоже на хлопот. Если функции были определены из классов типов, а не отдельно в каждом модуле, механика вывода типа компилятора могла бы решить эту проблему. Это также упростило бы переключение базовых контейнеров, пока они разделяли типы классов (т.е. Позволяет просто использовать Sequence вместо List для повышения эффективности случайного доступа).

Почему Haskell не имеет класс Collection и/или Indexable, чтобы унифицировать и обобщить некоторые из этих функций?

Ответ 1

Отчасти, причина в том, что монады и стрелы - новые, инновационные особенности Haskell, в то время как коллекции относительно более мирские. Haskell имеет долгую историю как язык исследования; интересные вопросы исследования (проектирование экземпляров монады и определение общих операций для монад) получают больше усилий по разработке, чем полировка "промышленная сила" (определение API-интерфейсов контейнеров).

Отчасти, причина в том, что эти типы поступают из трех разных пакетов (базы, контейнеров и векторов), с тремя отдельными историями и дизайнерами. Это затрудняет координацию их дизайнеров при предоставлении экземпляров любого класса одного типа.

Отчасти, причина в том, что определение класса одного типа для покрытия всех пяти указанных вами контейнеров очень сложно. Список, последовательность и вектор относительно похожи, но Map и Set имеют совершенно разные ограничения. Для List, Sequence и Vector вам нужен простой класс конструктора, но для Set это не будет работать, поскольку для Set требуется экземпляр Ord для типа элемента. Еще хуже то, что Map может поддерживать большинство ваших методов, но для его одноэлементной функции требуются два параметра, в которых остальным нужен только один.

Ответ 2

Пакет lens предоставляет некоторые из этих функций.

  • Тестирование пустоты, создание пустых контейнеров. Оба они предоставляются с помощью AsEmpty typeclass из Control.Lens.Empty.

  • Доступ к элементам по ключу/индексу. At и Ixed с Control.Lens.At.

  • Проверка членства в контейнерах типа. Contains typeclass из Control.Lens.At.

  • Добавление и удаление элементов в контейнеры, похожие на последовательности. Cons и Snoc с помощью Control.Lens.Cons.

Кроме того, метод pure класса Applicative может часто использоваться для создания контейнеров с одним синглтоном. Для вещей, которые не являются функторами/аппликациями в Haskell, например Set, возможно, point из Data.Pointed.

Ответ 3

Как указывали другие ответы, Haskell имеет тенденцию использовать разные словарные слова. Тем не менее, я не думаю, что они объяснили причину разницы очень хорошо.

На языке, подобном Java, функции не являются "гражданами первого класса"; это правда, что анонимные функции доступны в последних версиях, но этот стиль интерфейса (Collection, Indexable, Interable и т.д.) был разработан до этого.

Это заставляет утомительно передавать наш код, поэтому мы предпочитаем передавать данные другим людям в наш код. Например:

  • Данные, реализующие Java Iterable, позволяют нам писать for (Foo x : anIterable) { ... }
  • Данные, реализующие PHP ArrayAccess, позволяют нам писать anArrayAccess[anIndex]

Этот стиль также можно увидеть на языках OO, которые реализуют генераторы, поскольку это другой способ написать for yieldedElement in aGenerator: ....

Haskell использует другой подход со своими классами: мы предпочитаем, чтобы наш код передавался другим людям. Некоторые (упрощенные) примеры:

  • Functor принять наш код и применить его к любым элементам, которые они содержат '
  • Monad принять наш код и применить его в какой-то "последовательности"
  • Foldable принять наш код и использовать его для "сокращения" их содержимого.

Java требуется только Iterable, так как мы должны называть наш код в нашем цикле for, поэтому мы можем убедиться, что он правильно вызван. Haskell требует более специфических типов, поскольку другой код будет вызывать наш, поэтому нам нужно указать, как его следует вызывать; это a map, a fold, a unfold и т.д.

К счастью, система типов помогает нам выбрать правильный метод;)

Ответ 4

В Haskell есть классы классов для работы с коллекциями в базовом пакете: Functor, Foldable и Traversable может быть полезна для работы с коллекциями, а Monoid, Applicative и/или Alternative typeclasses могут быть полезны для создания коллекций.

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

null для тестирования "пустоты"

Складная поддержка null, поскольку база 4.8 (any (const True) является альтернативой для более ранних версий).

длина/размер элемента:

Складная поддержка length, поскольку база 4.8 (getSum . foldMap (const 1) является альтернативой для более ранних версий).

elem/member для включения включения

Складные опоры elem, notElem и member.

пустой и/или одноэлементный для построения по умолчанию

Для пустого есть mempty из Monoid и empty из Alternative. Для singleton существует pure от аппликативного.

объединение для объединения set

Существует mappend из Monoid и <|> из Alternative. Они не обязательно реализуют set union, но они реализуют некоторую форму объединения, которая хорошо работает вместе с пустым и обычно также с singleton и find.

(\)/diff для заданной разности

К сожалению, это не поддерживается.

(!)/(!!) для небезопасного индексирования (частичная функция)

Вы можете использовать fromJust вместе с функцией безопасной индексирования.

(!?)/поиск надежной индексации (общая функция)

Существует find из Foldable.

Ответ 5

Такие классные классы существуют в стандартном Haskell, но они не имеют того же названия, что и их эквивалентные аналоги OO. Collection typeclass, например, называется Foldable в Haskell. Вы можете использовать его для проверки, если структура пуста (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)

Элемент null и set union относятся к Monoid typeclass (где mappend == union). В этом свете установленная разница также может быть реализована в своем собственном typeclass Differentiable (который, я уверен, уже существует в десятках библиотек Haskell), и у нас будет полная совместимость с императивными языками.

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

Ответ 6

Законы. У хорошего стиля есть законы. Отличная модель имеет достаточную параметричность, так что ее законы являются "теоремами бесплатно". Класс без правил - это просто перегрузка имени ad-hoc.

Кроме того, ознакомьтесь с classy-prelude и Edison-API.

Ответ 7

У вас есть классы для разных аспектов сбора данных:

  • композиция: Monoid (модуль Data.Monoid)

  • последовательный контроль: аппликативный, Monad (модули Control.Applicative, Control.Monad)

  • последовательный состав: Alternative, MonadPlus (модули Control.Applicative, Control.Monad)

  • Непоследовательное отображение и сокращение: Functor (mod. Data.Functor), Foldable (mod. Data.Foldable)

  • последовательное отображение и сокращение: Traversable (модуль Data.Traversable)

  • Сериализация: двоичный (mod. Data.Binary)

  • сравнение: Eq, Ord (mod. Data.Eq, Data.Ord)

  • textualisation: Показать, прочитать

  • глубокая оценка (для нормальной формы): NFData (mod. Control.DeepSeq)

  • Обширная обходная способность: Data (mod. Data.Data)

За исключением того, что мономорфные коллекции (ByteString, IntSet, Text) не могут реализовать Functor и Foldable (они требуют типа arity == 1 (Вид: * → *))

Также ни (Set a) не реализует Functor.

Пакет mono-traversable переопределяет некоторые классы без исключения мономорфных типов.

Update. Существует попытка поместить большинство функций в класс с пакетами mono-traversable и classy-prelude.

библиотека ref, platform