Подтвердить что ты не робот

Почему Haskell перестает вызывать типы типов данных в сигнатурах функций?

Во-первых, этот вопрос не на 100% специфичен для Haskell, не стесняйтесь комментировать общий дизайн типов, интерфейсов и типов.

Я читаю LYAH - создание типов и классных классов Ниже приводится отрывок, в котором я ищу дополнительную информацию:

Data (Ord k) => Map k v = ...  

Однако это очень сильное соглашение в Haskell, чтобы никогда не добавлять typeclass ограничения в объявлениях данных. Зачем? Ну, потому что мы мало пользы, но мы в конечном итоге пишем еще класс ограничений, даже если нам не нужны их. Если положить или не положить Ord k ограничение в декларации данных для Карта k v, нам придется положить ограничение на функции, которые предположим, что ключи на карте могут быть приказал. Но если мы не ставим ограничение в декларации данных, мы не нужно ставить (Ord k) = > в тип деклараций функций, которые все равно, могут ли клавиши заказанный или нет. Пример такого функция isList, которая просто берет отображение и преобразование его в ассоциативный список. Его подпись типа is toList:: Map k a → [(k, a)]. Если Карта k v имела ограничение типа в ее объявление данных, тип toList должен быть toList:: (Ord k) = > Карта k a → [(k, a)], хотя функция не делает никакого сравнения по порядку.

Это сначала кажется логичным - но разве у него нет недостатка в том, что класс типа прикреплен к типу? Если typeclass - это поведение типа, то почему поведение должно определяться с помощью типа (через функции), а не самого типа? Я предполагаю, что есть мета-программирование, которое могло бы использовать его, и это, безусловно, хорошая и описательная кодовая документация. И наоборот, будет ли это хорошей идеей на других языках? Было бы идеальным указать интерфейс, который должен соответствовать объекту методу, например, если метод не используется вызывающим объектом, ему не нужно соответствовать интерфейсу? Более того, почему Haskell не делает вывод о том, что функция, использующая тип Foo, должна извлекать ограничения типа typeclass, указанные в типе Foo declare? Есть ли прагма для этого?

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

Итак, я здесь.

4b9b3361

Ответ 1

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

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

Например, Data.List содержит множество функций, для которых требуется (Eq a), но, конечно, списки отлично подходят без этого ограничения.

Ответ 2

Короткий ответ: Haskell делает это, потому что так написано спецификацию языка.

В длинном ответе содержится цитата из раздела Расширения языка документации GHC:

Любой тип данных, который может быть объявлен в стандартном синтаксисе Haskell-98, также может быть объявлен с использованием синтаксиса GADT-стиля. Выбор во многом стилистичен, но декларации в стиле GADT отличаются в одном важном отношении: они трактуют ограничения классов на конструкторах данных по-разному. В частности, если конструктору задан контекст типа-типа, этот контекст становится доступным путем сопоставления шаблонов. Например:

data Set a where
    MkSet :: Eq a => [a] -> Set a

(...)

Все это поведение контрастирует с Haskell 98 с особым отношением контекстов к объявлению типа данных (Раздел 4.2.1 отчета Haskell 98). В Haskell 98 определение

data Eq a => Set' a = MkSet' [a]

дает MkSet 'тот же тип, что и MkSet выше. Но вместо того, чтобы предоставить ограничение (Eq a), для сопоставления шаблонов на MkSet требуется ограничение (Eq a)! GHC добросовестно реализует это поведение, хотя и нечетное. Но для объявлений в стиле GADT поведение GHC намного более полезно, а также гораздо более интуитивно понятное.

Ответ 3

Основная причина избегать ограничений типов в объявлениях данных заключается в том, что они абсолютно ничего не выполняют; на самом деле, я считаю, что GHC ссылается на такой контекст класса, как "глупый контекст". Причина этого в том, что словарь классов не переносится со значениями типа данных, поэтому вы должны добавить его ко всем функциям, действующим на значения в любом случае.

В качестве способа "принудительного" ограничения типа typeclass для функций, работающих с типом данных, он также не выполняет ничего; функции, как правило, должны быть как можно более полиморфными, поэтому зачем принуждать ограничение к вещам, которые ему не нужны?

В этот момент вы можете подумать, что можно изменить семантику ADT, чтобы переносить словарь со значениями. На самом деле, похоже, это весь смысл GADT s; например, вы можете:

data Foo a where { Foo :: (Eq a) => a -> Foo a }
eqfoo :: Foo t -> Foo t -> Bool
eqfoo (Foo a) (Foo b) = a == b

Обратите внимание, что тип eqfoo не нуждается в ограничении Eq, поскольку он "переносится" самим типом данных Foo.

Ответ 4

Я хотел бы указать, что если вы опасаетесь, что можно построить объект, требующий ограничений для его операций, но не для его создания, скажем mkFoo, вы всегда можете искусственно установить ограничение на функцию mkFoo для принудительного применения использование typeclass для людей, которые используют код. Идея также распространяется на функции типа non mkFoo, которые работают на Foo. Затем при определении модуля не экспортируйте ничего, что не применяет ограничения.

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