如果类实例是一个循环,GHC可以发出警告吗?

时间:2012-09-04 19:40:04

标签: haskell

真实世界Haskell有这个例子:

class BasicEq3 a where
    isEqual3 :: a -> a -> Bool
    isEqual3 x y = not (isNotEqual3 x y)

    isNotEqual3 :: a -> a -> Bool
    isNotEqual3 x y = not (isEqual3 x y) 

instance BasicEq3 Bool

当我在GHCI中运行时:

#> isEqual3 False False
out of memory

所以,你必须实现两种方法中的至少一种,否则它将循环。而且你可以灵活地选择哪一个很整洁。

我的问题是,如果没有覆盖足够的默认值并且默认值形成循环,是否有办法获得警告或其他内容?对我来说,看起来很奇怪,这个例子很简单的编译器很好。

5 个答案:

答案 0 :(得分:12)

不,我担心GHC不做那样的事情。这也是不可能的一般

您可以看到,类型类的方法可以以有用的方式相互递归。这是一个这种类型类的人为例子。完全没有定义sumOddssumEvens,即使它们的默认实现是相互关联的。

class Weird a where
    measure :: a -> Int

    sumOdds :: [a] -> Int
    sumOdds [] = 0
    sumOdds (_:xs) = sumEvens xs

    sumEvens :: [a] -> Int
    sumEvens [] = 0
    sumEvens (x:xs) = measure x + sumOdds xs

答案 1 :(得分:12)

我认为GHC在“不间断”循环依赖的情况下发出警告是完全没问题的。甚至还有一张票:http://hackage.haskell.org/trac/ghc/ticket/6028

仅仅因为某些事情是“不可判定的”并不意味着没有问题的实例能够得到有效解决。 GHC(或任何其他Haskell编译器)已经拥有它需要的相当多的信息,并且如果用户在没有“破坏”循环依赖性的情况下实例化类,它就完全有可能发出警告。如果编译器在极少数情况下错误,如前面的帖子所示,则用户可以使用-nowarnundefinedcyclicmethods或类似的机制来告诉GHC保持安静。在几乎所有其他情况下,警告将是最受欢迎的,并将增加程序员的工作效率;避免什么几乎总是一个愚蠢的错误。

答案 2 :(得分:6)

不,没有,因为如果编译器可以做出这个决定,那就相当于解决停机问题。一般来说,两个函数以“循环”模式相互调用的事实不足以得出结论,实际调用其中一个函数将导致循环。

使用(人为的)例子,

collatzOdd :: Int -> Int
collatzOdd 1 = 1
collatzOdd n = let n' = 3*n+1 in if n' `mod` 2 == 0 then collatzEven n'
                                   else collatzOdd n'

collatzEven :: Int -> Int
collatzEven n = let n' = n `div` 2 in if n' `mod` 2 == 0 then collatzEven n'
                                        else collatzOdd n'

collatz :: Int -> Int
collatz n = if n `mod` 2 == 0 then collatzEven n else collatzOdd n

(这当然不是实现Collatz conjecture的最自然的方式,但它说明了相互递归的函数。)

现在collatzEvencollatzOdd相互依赖,但Collat​​z猜想声明,collatz的所有正面n都会终止。如果GHC可以确定具有collatzOddcollatzEven作为默认定义的类是否具有完整定义,那么GHC将能够解决Collat​​z猜想! (这当然不是停止问题不可判断性的证明,但它应该说明为什么确定一个相互递归的函数集是否定义良好并不像它看起来那么微不足道。)

通常,由于GHC无法自动确定,因此Haskell类的文档将提供创建类实例所需的“最小完整定义”。

答案 3 :(得分:2)

我不这么认为。我担心您期望编译器解决暂停问题!仅仅因为两个函数是相互定义的,并不意味着它是一个错误的默认类。此外,我过去曾使用过类,我只需编写instance MyClass MyType来添加有用的功能。因此,要求编译器警告您该类是否要求它抱怨其他有效的代码。

[当然,在开发过程中使用ghci并在编写完成后测试每个函数! 使用HUnit和/或QuickCheck,只是为了确保最终代码中没有任何内容结束。]

答案 4 :(得分:2)

在我个人看来,违约机制是不必要的,也是不明智的。类作者很容易将默认值作为普通函数提供:

notEq3FromEq3 :: (a -> a -> Bool) -> (a -> a -> Bool)
notEq3FromEq3 eq3 = (\x y -> not (eq3 x y))

eq3FromNotEq3 :: (a -> a -> Bool) -> (a -> a -> Bool)
eq3FromNotEq3 ne3 = (\x y -> not (ne3 x y))

(事实上,这两个定义是相同的,但总的来说并非如此)。然后一个实例看起来像:

 instance BasicEq3 Bool where
   isEqual3 True True = True
   isEqual3 False False = True
   isEqual3 _ _ = False

   isNotEqual3 = notEq3FromEq3 isEqual3

并且不需要默认值。然后GHC可以警告你,如果你不提供定义,任何不愉快的循环必须由你明确写入你的代码。

这确实消除了使用默认定义向具有默认定义的类添加新方法的整洁能力,而不会影响现有实例,但这在我看来并不是那么大的好处。上述方法原则上也更灵活:你可以例如提供允许Ord实例选择任何比较运算符来实现的函数。