13

我知道这是代码有点傻,但是有人可以解释为什么这会isList [42]返回TrueisList2 [42]prints False,以及如何防止这种情况?我想更好地理解一些更晦涩的 GHC 类型扩展,我认为这将是一个有趣的例子。

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE IncoherentInstances #-}

class IsList a where
  isList :: a -> Bool

instance IsList a where
  isList x = False

instance IsList [a] where
  isList x = True

isList2 = isList

main = 
  print (isList 42) >> 
  print (isList2 42) >> 
  print (isList [42]) >> 
  print (isList2 [42]) 
4

2 回答 2

15

这真的很简单。让我们问一下 GHCi 的类型isList2是什么:

∀x. x ⊢ :t isList2
isList2 :: a -> Bool

这与实例不匹配[a](即使它可以通过统一),但它确实a立即匹配实例。因此,GHC 选择a实例,因此isList2返回False.

这种行为恰恰是什么IncoherentInstances意思。实际上,这是一个很好的演示。


有趣的是,如果你简单地禁用IncoherentInstances,我们会得到完全相反的效果,GHCi 现在这样说:

∀x. x ⊢ :t isList2
isList2 :: [Integer] -> Bool

发生这种情况isList2是因为顶层绑定没有使用函数语法定义,因此受到 Dreaded Monomorphism Restriction 的约束。所以它被专门用于它实际使用的实例。

添加NoMonomorphismRestriction和禁用IncoherentInstances,我们得到了这个:

∀x. x ⊢ :t isList2
isList2 :: IsList a => a -> Bool
∀x. x ⊢ isList2 'a'
False
∀x. x ⊢ isList2 "a"
True
∀x. x ⊢ isList2 undefined

<interactive>:19:1:
    Overlapping instances for IsList a0 arising from a use of `isList2'

这是预期的重叠行为,如果选择不明确,则根据使用和投诉选择实例。


关于对问题的编辑,我不相信没有类型注释就可以获得所需的结果。

第一个选项是提供isList2类型签名,以防止IncoherentInstances过早选择实例。

isList2 :: (IsList a) => a -> Bool
isList2 = isList

您可能需要在其他任何isList提到(甚至是间接)的地方做同样的事情,而不是应用于一个论点。

第二个选项是消除数字文字的歧义并禁用IncoherentInstances.

main = 
  print (isList (42 :: Integer)) >> 
  print (isList2 (42 :: Integer)) >> 
  print (isList [42]) >> 
  print (isList2 [42]) 

在这种情况下,有足够的信息来选择一个最具体的实例,OverlappingInstances它的事情也是如此。

于 2013-04-17T03:08:30.177 回答
2

下面的代码不需要IncoherentInstances

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}

class IsList a where
  isList :: a -> Bool

instance IsList a where
  isList x = False

instance IsList [a] where
  isList x = True

isList2 :: (IsList a) => a -> Bool
isList2 = isList

main = do
  print (isList (42 :: Int))
  print (isList [42 :: Int])
  print (isList2 (42 :: Int))
  print (isList2 [42 :: Int])

我建议不要使用IncoherentInstances,这似乎会造成很多麻烦,因为您可以很容易地根据上下文默默地调用不同的重载。

于 2013-04-17T05:55:25.417 回答