真实世界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
所以,你必须实现两种方法中的至少一种,否则它将循环。而且你可以灵活地选择哪一个很整洁。
我的问题是,如果没有覆盖足够的默认值并且默认值形成循环,是否有办法获得警告或其他内容?对我来说,看起来很奇怪,这个例子很简单的编译器很好。
答案 0 :(得分:12)
不,我担心GHC不做那样的事情。这也是不可能的一般。
您可以看到,类型类的方法可以以有用的方式相互递归。这是一个这种类型类的人为例子。完全没有定义sumOdds
或sumEvens
,即使它们的默认实现是相互关联的。
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的最自然的方式,但它说明了相互递归的函数。)
现在collatzEven
和collatzOdd
相互依赖,但Collatz猜想声明,collatz
的所有正面n
都会终止。如果GHC可以确定具有collatzOdd
和collatzEven
作为默认定义的类是否具有完整定义,那么GHC将能够解决Collatz猜想! (这当然不是停止问题不可判断性的证明,但它应该说明为什么确定一个相互递归的函数集是否定义良好并不像它看起来那么微不足道。)
通常,由于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
实例选择任何比较运算符来实现的函数。