不是Monad约束

时间:2014-01-08 17:53:47

标签: haskell typeclass

是否可以为“not a monad”定义实例约束,以便定义两个非重叠实例,一个用于monadic值,另一个用于非monadic值?

简化示例:

{-# 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,如用户2407038及时注明)实际上是完全不同的,因此它甚至不符合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

Etvoilà!

在最后一个参数中使用(m b),我试图指出最终结果与第二个和第三个参数的类型相同。但问题似乎是FunctionalDependencies扩展名不会使类型的实例选择OverlappingInstances,因此将b(m b)视为“相同”为了它的目的。这种解释是正确的,还是我还缺少某些东西?

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

在阅读了更多有关此内容的材料之后,我高度推荐阅读this article by Oleg,在那里他概括了我和user2407038相关联的以前的解决方案。我发现它很容易接近。

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

1 个答案:

答案 0 :(得分:10)

您不需要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,whatIs a == String'。 第二个实例声明'对于任何类型(m a),whatIs(m a)==(m String)'。显然m aa的实例化(任何类型都是自由类型变量的实例化),但String永远不是m String的实例化。

为什么这有关系?当编译器检查fundeps是否冲突时,它只查看实例头;也就是=>右边的部分。因此,

instance (Show a, b ~ String) => WhatIs a b where

说'对于任何类型a,b,whatIs a == b'。显然,由于ab都是自由变量,因此可以使用任何其他类型对它们进行实例化。因此,如果a == (m a0)您可以自由地说b == (m String)。当且仅当第一个实例是匹配的实例时,b必须是字符串才会知道

由于任何类型都匹配abWhatIs (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