Как мне написать: "если typeclass a, то a также является экземпляром b по этому определению". - программирование
Подтвердить что ты не робот

Как мне написать: "если typeclass a, то a также является экземпляром b по этому определению".

У меня есть typeclass MyClass, и в нем есть функция, которая создает 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

который дает ошибку, Class MyClass 'используется как тип`.

Как я могу правильно выразить эти отношения в 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
4b9b3361

Ответ 1

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

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

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

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

"Undecidable" означает, что GHC не может доказать, что typechecking завершится. Хотя это звучит опасно, худшее, что может случиться от включения UndecidableInstances, заключается в том, что компилятор будет зацикливаться, в конечном счете заканчивая после исчерпания стека. Если он компилируется, то очевидно, что проверка типов завершена, поэтому проблем нет. Опасное расширение - IncoherentInstances, что так же плохо, как кажется.

Изменить: возникает другая проблема, вызванная этим подходом:

instance MyClass a => Show a where

data MyFoo = MyFoo ... deriving (Show)

instance MyClass MyFoo where

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

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

Ответ 2

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

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.

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

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 

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

Ответ 3

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

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

myShow :: MyClass a => a -> String
myShow = show

Ответ 4

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

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

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

Ответ 6

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

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    someFunc :: a -> Int

class MyShow a where
    myShow :: a -> String
    default myShow :: MyClass a => a -> String
    myShow = show . someFunc

instance MyClass Int where
    someFunc i = i

instance MyShow Int

main = putStrLn (myShow 5)

Обратите внимание, что единственный реальный шаблон (ну, кроме всего примера) уменьшен до instance MyShow Int.

См. aeson ToJSON для более реалистичного примера.