Можно ли определить ограничение экземпляра для "не монады", чтобы определить два неперекрывающихся экземпляра, один для монадических значений, другой для немонодических значений?
Упрощенный пример:
{-# 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 представляются как более гибкое решение для той же проблемной области, интересно, могли ли они использовать их для решения этого по-другому. Решение Олега, упомянутое выше, конечно же использует их, конечно.