Как я пишу, “если typeclass a, то также экземпляра b по этому определению”.

У меня есть typeclass MyClass, и существует функция в нем, которая производит a String. Я хочу использовать это для допущения экземпляра Show, так, чтобы я мог передать реализацию типов MyClass кому: show. До сих пор я имею,

class MyClass a where
    someFunc :: a -> a
    myShow :: a -> String 

instance MyClass a => Show a where
    show a = myShow a

который дает ошибку Constraint is no smaller than the instance head. Я также попробовал,

class MyClass a where
    someFunc :: a -> a
    myShow :: a -> String

instance Show (MyClass a) where
    show a = myShow a

который дает ошибку, ClassMyClass, используемый в качестве типа'.

Как я могу правильно выразить эти отношения в Haskell?Спасибо.

Я должен добавить, что хочу развить это с определенными экземплярами MyClass это испускает определенные строки на основе их типа. Например,

data Foo = Foo
data Bar = Bar

instance MyClass Foo where
    myShow a = "foo"

instance MyClass Bar where
    myShow a = "bar"

main = do
    print Foo
    print Bar
49
задан ДМИТРИЙ МАЛИКОВ 29 July 2012 в 07:05
поделиться

5 ответов

(Правка: оставить тело для потомков, но перейти к концу для реального решения)

В объявлении экземпляр MyClass a => Show a, давайте рассмотрим ошибку «Ограничение не меньше головы экземпляра». Ограничением является ограничение класса типа слева от '=>', в данном случае MyClass a. «Заголовок экземпляра» — это все после класса, для которого вы пишете экземпляр, в данном случае a (справа от Show).Одно из правил вывода типов в GHC требует, чтобы ограничение имели меньше конструкторов и переменных, чем head. Это часть того, что называется «Условия Патерсона». Они существуют как гарантия того, что проверка типов завершится.

В этом случае ограничение точно такое же, как и у головы, т.е. a, поэтому оно не проходит этот тест. Вы можете удалить проверки условий Патерсона, включив UndecidableInstances, скорее всего, с помощью прагмы {-# LANGUAGE UndecidableInstances #-}.

В этом случае вы, по сути, используете свой класс MyClass в качестве синонима typeclass для класса Show. Создание синонимов классов, подобных этому, является одним из канонических применений расширения UndecidableInstances, поэтому вы можете безопасно использовать его здесь.

'Неразрешимо' означает, что GHC не может доказать, что проверка типа прекратится. Хотя это звучит опасно,худшее, что может произойти при включении UndecidableInstances, заключается в том, что компилятор будет циклироваться, в конечном итоге завершая работу после исчерпания стека. Если он компилируется, то очевидно проверка типа прекращается, поэтому проблем нет. Опасным расширением является IncoherentInstances, что так же плохо, как и звучит.

Edit: еще одна проблема, которая стала возможной благодаря такому подходу, возникает из-за этой ситуации:

instance MyClass a => Show a where

data MyFoo = MyFoo ... deriving (Show)

instance MyClass MyFoo where

Теперь есть два экземпляра Show для MyFoo, один из производного предложения и один для экземпляров MyClass. Компилятор не может решить, что использовать, поэтому он будет выручать с сообщением об ошибке. Если вы пытаетесь создать MyClass неуказуемые типы, которые уже имеют экземпляры Show, вам придется использовать newtypes, чтобы скрыть уже существующие экземпляры Show.Даже типы без экземпляров MyClass все равно будут конфликтовать, потому что определение экземпляр MyClass => Show a, потому что определение фактически предоставляет реализацию для всех возможных a (проверка контекста приходит позже; она не связана с выбором экземпляра)

Так что это сообщение об ошибке и то, как UndecidableInstances заставляет его исчезать. К сожалению, это много проблем для использования в реальном коде, по причинам, которые объясняет Эдвард Кметт. Первоначальным стимулом было избежать указания ограничения Show, когда уже есть ограничение MyClass. Учитывая это, я бы просто использовал myShow из MyClass вместо show. Вам вообще не понадобится ограничение Show.

29
ответ дан 7 November 2019 в 11:39
поделиться

Я думаю, что было бы лучше сделать это наоборот:

class Show a => MyClass a where
    someFunc :: a -> a

myShow :: MyClass a => a -> String
myShow = show
5
ответ дан 7 November 2019 в 11:39
поделиться

Вы можете скомпилировать его, но не с Haskell 98. Вы должны включить некоторые языковые расширения:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
-- at the top of your file

Гибкие экземпляры позволяют разрешить контекст в объявлении экземпляра. Я действительно не знаю значения UndecidableInstances, но я бы избегал как можно большего.

2
ответ дан 7 November 2019 в 11:39
поделиться

Я хочу категорически не согласиться с предложенными на данный момент неработающими решениями.

instance MyClass a => Show a where
    show a = myShow a

Из-за того, как работает разрешение экземпляров, это очень опасный экземпляр, чтобы иметь его поблизости!

Разрешение экземпляра происходит путем эффективного подбора шаблона в правой части => каждого экземпляра, совершенно не обращая внимания на то, что находится слева от =>.

Когда ни один из этих экземпляров не пересекается, это прекрасная вещь. Однако, то, что вы здесь говорите: "Вот правило, которое вы должны использовать для КАЖДОГО экземпляра шоу. Когда вас просят создать экземпляр Show для любого типа, вам понадобится экземпляр MyClass, так что возьмите его, и вот реализация." -- как только компилятор принял решение использовать ваш экземпляр, (просто в силу того, что 'a' объединяется со всем) у него нет шансов отступить и использовать любые другие экземпляры!

Если вы включите {-# LANGUAGE OverlappingInstances, IncoherentInstances #-} и т.д., чтобы заставить его компилировать, вы получите не очень заметные сбои, когда будете писать модули, импортирующие модуль, предоставляющий это определение, и нуждающиеся в использовании любого другого экземпляра Show. В конце концов, вы сможете заставить этот код компилироваться с достаточным количеством расширений, но он, к сожалению, не будет делать то, что, по вашему мнению, он должен делать!

Если вы подумаете об этом с учетом:

instance MyClass a => Show a where
    show = myShow

instance HisClass a => Show a where
    show = hisShow

что должен выбрать компилятор?

Ваш модуль может определить только один из них, но код конечного пользователя будет импортировать множество модулей, а не только ваш. Кроме того, если другой модуль определяет

instance Show HisDataTypeThatHasNeverHeardOfMyClass

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

Правильный ответ, к сожалению, состоит в том, чтобы сделать две вещи.

Для каждого отдельного экземпляра MyClass вы можете определить соответствующий экземпляр Show с очень механическим определением

instance MyClass Foo where ...

instance Show Foo where
    show = myShow

Это довольно неудачно, но хорошо работает, когда рассматривается всего несколько экземпляров MyClass.

Когда у вас большое количество экземпляров, способ избежать дублирования кода (для случаев, когда класс значительно сложнее, чем show) - определить.

newtype WrappedMyClass a = WrapMyClass { unwrapMyClass :: a }

instance MyClass a => Show (WrappedMyClass a) where
    show (WrapMyClass a) = myShow a

Это обеспечивает новый тип как средство для диспетчеризации экземпляров. и затем

instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...

является однозначным, потому что "шаблоны" типов для WrappedFoo a и WrappedBar a не совпадают.

В пакете base есть несколько примеров этой идиомы.

В Control.Applicative есть определения для WrappedMonad и WrappedArrow именно по этой причине.

В идеале вы могли бы сказать:

instance Monad t => Applicative t where
    pure = return
    (<*>) = ap 

но фактически этот экземпляр говорит, что каждый Applicative должен быть получен путем первого нахождения экземпляра Monad, а затем диспетчеризации к нему. Таким образом, хотя по замыслу можно сказать, что каждая монада является аппликативной (по тому, как читается импликация =>), на самом деле она говорит, что каждая аппликативная монада является монадой, потому что наличие экземпляра, возглавляющего 't', соответствует любому типу. Во многих отношениях синтаксис для определений 'instance' и 'class' является обратным.

58
ответ дан 7 November 2019 в 11:39
поделиться

You may find some interesting answers in a related SO question: Linking/Combining Type Classes in Haskell

1
ответ дан 7 November 2019 в 11:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: