Не ограничение Монады
Можно ли определить ограничение экземпляра для "не монады", чтобы определить два неперекрывающихся экземпляра, один для монадических значений, другой для немонодических значений?
Упрощенный пример:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverlappingInstances #-}
class WhatIs a b | a -> b where
whatIs :: a -> b
instance (Show a) => WhatIs a String where
whatIs = show
instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
whatIs x = fmap show x
main :: IO ()
main = do
let x = 1 :: Int
putStrLn "----------------"
{-print $ whatIs (1::Int)-}
print $ whatIs (Just x)
putStrLn "--- End --------"
Итак, я использую FunctionalDependencies, чтобы избежать аннотаций типа, но, разумеется, компилятор жалуется на
Functional dependencies conflict between instance declarations:
instance [overlap ok] Show a => WhatIs a String
-- Defined at test.hs:10:10
instance [overlap ok] (Monad m, Functor m, Show a) =>
WhatIs (m a) (m String)
-- Defined at test.hs:13:10
Потому что a
может принимать значение m a
, и, следовательно, возникает конфликт.
Однако, если бы я мог заменить первый экземпляр чем-то вроде:
instance (NotAMonad a, Show a) => WhatIs a String where
whatIs = show
Эта проблема не представилась бы сама.
До сих пор я нашел это очень старое письмо, которое, кажется, предлагает несколько смежное решение, но я не уверен, что есть новые методы для решения этой проблемы...
Я также нашел пакет constraints, который, я уверен, имеет полезные функции для этого случая, но ему очень не хватает ( простые) примеры.
Любые подсказки?
Изменить: после user2407038 правильный ответ.
Итак, я попробовал ответить user2407038 ниже, и мне действительно удалось собрать предоставленный пример. Вывод? Я не должен был так упростить пример. После некоторого размышления с моим фактическим примером, я смог уменьшить его до этого:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module IfThenElseStackExchange where
class IfThenElse a b c d | a b c -> d where
ifThenElse :: a -> b -> c -> d
instance (Monad m) => IfThenElse (m Bool) (m b) (m b) (m b) where
ifThenElse m t e = do
b <- m
if b then t else e
instance (Monad m) => IfThenElse (m Bool) b b (m b) where
ifThenElse ma t e = do
a <- ma
return $ if a then t else e
Но я все еще получаю страшную ошибку Functional dependencies conflict between instance declarations
. Зачем? Часть после =>
(глава экземпляра, как указано в user2407038) на самом деле совсем другая, поэтому она даже не подходит для OverlappingInstances, так как компилятор может выбрать наиболее конкретный.
Тогда что?
Ошибка, как всегда, указана сообщением об ошибке. Часть a b c d | a b c -> d
не соблюдается приведенным выше кодом. Поэтому я, наконец, попробовал это:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module IfThenElseStackExchange where
class IfThenElse a b c d | a b c -> d where
ifThenElse :: a -> b -> c -> d
instance (Monad m, c ~ b) => IfThenElse (m Bool) (m b) (m b) (m c) where
ifThenElse m t e = do
b <- m
if b then t else e
instance (Monad m, c ~ b) => IfThenElse (m Bool) b b (m c) where
ifThenElse ma t e = do
a <- ma
return $ if a then t else e
Et voilà!
Используя (m b)
в последнем параметре, я пытался указать, что конечный результат имеет тот же тип, что и второй и третий параметры. Но проблема заключается в том, что расширение FunctionalDependencies
не делает тот же тип экземпляра, который выбирается на типах как OverlappingInstances, и поэтому рассматривает b
и (m b)
"то же самое" для его целей. Правильно ли эта интерпретация, или я все еще что-то пропущу?
Я все еще могу сказать "компилятору, что c
имеет тот же тип, что и b
, используя constrain c ~ b
и, таким образом, достигая намеченного результата.
Прочитав еще несколько материалов об этом, я очень рекомендую читать статью этой статьи Олега, где он обобщает свои прежние решения, которые связаны как с I, так и с user2407038, Я нашел его вполне доступным.
Если моя интерпретация FunctionalDependencies выше правильная, и TypeFamilies представляются как более гибкое решение для той же проблемной области, интересно, могли ли они использовать их для решения этого по-другому. Решение Олега, упомянутое выше, конечно же использует их, конечно.
Ответы
Ответ 1
Вам не нужен класс NotAMonad
, или, скорее, WhatIs
уже этот класс.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances,
TypeFamilies, UndecidableInstances, IncoherentInstances #-}
class WhatIs a b | a -> b where
whatIs :: a -> b
instance (Show a, b ~ String) => WhatIs a b where
whatIs = show
instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
whatIs x = fmap show x
Вам не нужно строго IncoherentInstances
, но если вы хотите, чтобы такие вещи, как whatIs (1 :: Num a => a)
, работали, вам это нужно.
Это, вероятно, не самый лучший способ сделать то, что вы хотите, но ваш случай использования не ясен.
Изменить: больше объяснений:
Прежде всего: эти экземпляры не перекрываются! Я включил полный список языковых прагм. Вы указали, что "Функциональные зависимости конфликтуют между объявлениями экземпляра". Скажем, у вас есть следующее:
class C a b | a -> b
Предположим, что у вас есть два экземпляра C
: C a b
, C c d
(здесь a
не является жесткой переменной типа, это просто любой тип haskell). Если a
является экземпляром C
(или наоборот), то b
должен быть экземпляром d
. Это общее правило может быть несколько абстрактным, поэтому давайте посмотрим на ваш код:
instance (Show a) => WhatIs a String where
instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
Так как a
- это любой тип, вы объявляете, что "для любого типа a, whatIs a == String".
Второй экземпляр объявляет, что "для любого типа (m a), whatIs (m a) == (m String)". Очевидно, что m a
является экземпляром a
(любой тип является экземпляром переменной свободного типа), но String
никогда не является экземпляром m String
.
Почему все это имеет значение? Когда компилятор проверяет, конфликтуют ли фонды, он смотрит только на голову экземпляра; то есть участок справа от =>
. Следовательно,
instance (Show a, b ~ String) => WhatIs a b where
говорит "для любых типов a, b, whatIs a == b". Очевидно, что поскольку a
и b
являются свободными переменными, они могут быть созданы с любым другим типом. Поэтому, если a == (m a0)
, вы можете свободно сказать, что b == (m String)
. Тот факт, что b
должен быть строкой, становится известным тогда и только тогда, когда первый экземпляр совпадает с ним.
Так как любые типы соответствуют a
и b
, WhatIs (IO ()) b
соответствует первому экземпляру. Второй экземпляр используется, потому что компилятор попытается сопоставить экземпляры в порядке их специфичности. Здесь вы можете найти "правила" для определения специфики.. Простое объяснение состоит в том, что WhatIs a b
соответствует большему количеству вещей, поэтому он более общий и будет использоваться позже. (Фактически, экземпляр C a0 a1 a2 .. an
, где a*
является отдельной переменной типа, является наиболее общим экземпляром и всегда будет проверяться последним)
Изменить: Вот общее решение вашей проблемы. С помощью этого кода whatIs = f_map show
.