是否可以为“非单子(monad)”定义实例约束,以便定义两个非重叠的实例,一个用于单子(monad)值,另一个用于非单子(monad)值?

一个简化的例子:

{-# 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

这个问题不会出现。

到目前为止,我已经发现了这个very old email似乎提出了一些相关的解决方案,但是我不确定是否有新技术可以解决这个问题...

我还找到了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错误。为什么?实际上,=>之后的部分(即instance head,如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

等等!

通过在最后一个参数中使用(m b),我试图表明最终结果与第二个和第三个参数具有相同的类型。但是问题似乎是FunctionalDependencies扩展没有在类型上选择与OverlappingInstances相同的实例,因此出于其目的将b(m b)视为“相同”。这种解释是正确的,还是我仍然缺少什么?

我仍然可以使用约束c“告诉”编译器bc ~ b具有相同的类型,从而达到预期的结果。

在阅读了更多有关此内容的 Material 之后,我强烈建议阅读this article by Oleg,他在其中推广了我和user2407038都链接的以前的解决方案。我发现它很容易访问。

如果我对上面的FunctionalDependencies的解释是正确的,并且TypeFamilies被提出为针对同一问题域的更灵活的解决方案,我想知道是否可以使用它们以另一种方式解决此问题。当然,上面提到的Oleg解决方案肯定会使用它们。

最佳答案

您不需要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 bC c d(此处a不是固定类型变量;它只是任何haskell类型)。如果ac的实例化(反之亦然),则b必须是d的实例化。该一般规则可能有点抽象,因此让我们看一下您的代码:
instance (Show a) => WhatIs a String where
instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where

由于a是任何类型,因此您要声明“对于任何类型a,什么是==字符串”。
第二个实例声明“对于任何类型(m a),whatIs(m a)==(m字符串)”。显然,m aa的实例化(任何类型都是自由类型变量的实例化),但是String绝不是m String的实例化。

为什么这有关系?当编译器检查眼底是否冲突时,它只会看实例头;也就是=>右侧的部分。因此,
instance (Show a, b ~ String) => WhatIs a b where

在说“对于任何类型a,b,a == b是什么”。显然,由于ab都是自由变量,因此可以用任何其他类型实例化它们。因此,如果使用a == (m a0),您可以自由地说出b == (m String)。仅当第一个实例是匹配的实例时,b必须是字符串的事实才变得众所周知。

由于任何类型都匹配ab,因此WhatIs (IO ()) b匹配第一个实例。之所以使用第二个实例,是因为编译器将尝试按特定顺序匹配这些实例。 You can find the 'rules' for determining specificity here.。简单的解释是WhatIs a b可以匹配更多的东西,因此它更通用,将在以后使用。 (实际上,实例C a0 a1 a2 .. an其中a*是一个不同的类型变量,是最通用的实例,将始终在最后尝试)

编辑:Here is the general solution to your problem.使用此代码whatIs = f_map show

08-18 18:57