2010-01-31 3 views
12

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

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

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

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

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

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

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

ответ

4

Основная причина, по которой избегать ограничений типов в декларациях данных заключается в том, что они ничего не выполняют; на самом деле, я считаю, что 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.

+0

Я довольно новичок в Haskell, краткое объяснение того, что GADT было бы полезным (или ссылкой). –

+0

Я немного переработал этот раздел и добавил ссылку на документацию GHC для GADT. Если кто-то хочет взломать лучшее объяснение того, что такое GADT, это было бы очень приятно! – mithrandi

9

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

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

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

+1

«нет возможности построить карту без нее». - именно по этой причине я всегда чувствовал, что такие вещи, как контексты Орда, для функций, которые могут быть дублированы, являются избыточными, а также причиной совпадений GADT с контекстами. Тот факт, что у меня есть «Map k v», уже достаточно доказан, что k упорядочен. Если все, что я делаю, это извлечение вещей с карты, например, зачем мне нужен экземпляр Ord? – mokus

7

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

Длинный ответ включает в себя процитировать из GHC documentation language extensions section:

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

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

(...)

Все это поведение контрастирует с трепетным лечения Хаскелла 98 контекстов на объявлении типа данных (Section 4.2.1 of the Haskell 98 Report). В Haskell 98 Определение

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

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

1

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

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

Смежные вопросы