Почему Haskell останавливается на выводе классов типов данных в сигнатурах функций? - PullRequest
15 голосов
/ 31 января 2010

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

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

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

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

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

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

Так вот и я.

Ответы [ 4 ]

9 голосов
/ 31 января 2010

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

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

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

8 голосов
/ 31 января 2010

Короткий ответ: 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 гораздо более полезно, а также гораздо более интуитивно.

6 голосов
/ 31 января 2010

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

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

В этот момент вы можете подумать, что должна быть возможность изменить семантику 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.

1 голос
/ 31 января 2010

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

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

...