13

真实世界的 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

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

我的问题是,如果没有覆盖足够的默认值并且默认值形成循环,是否有办法获得警告或其他东西?对我来说很奇怪,如此疯狂聪明的编译器在这个例子中很好。

4

5 回答 5

12

不,我担心 GHC 不会做那样的事情。这通常也是不可能

你看,类型类的方法可以以一种有用的方式相互递归。这是这种类型类的人为示例。sumOdds不定义or是完全可以的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
于 2012-09-04T19:49:57.027 回答
12

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

仅仅因为某事“无法确定”并不意味着问题的任何实例都无法有效解决。GHC(或任何其他 Haskell 编译器)已经拥有相当多的所需信息,如果用户在没有“破坏”循环依赖的情况下实例化一个类,它完全有可能发出警告。如果编译器在以前的帖子中举例说明的极少数情况下出错,那么用户可以有一个-nowarnundefinedcyclicmethods或类似的机制来告诉 GHC 保持安静。在几乎所有其他情况下,警告都会受到欢迎,并且会提高程序员的工作效率。避免几乎总是一个愚蠢的错误。

于 2012-09-04T21:15:46.983 回答
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

(这当然不是实现Collat​​z 猜想的最自然的方式,但它说明了相互递归的函数。)

现在collatzEvencollatzOdd相互依赖,但 Collat​​z 猜想指出,调用collatz终止于所有 positive n。如果 GHC 可以确定一个具有collatzOdd并且collatzEven作为默认定义的类是否具有完整的定义,那么 GHC 将能够解决 Collat​​z 猜想!(这当然不能证明停止问题的不可判定性,但它应该说明为什么确定相互递归的函数集是否定义明确并不像看起来那么简单。)

一般来说,由于 GHC 不能自动确定这一点,Haskell 类的文档将给出创建类实例所需的“最小完整定义”。

于 2012-09-04T19:59:14.273 回答
2

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

[当然,在开发过程中使用ghci,并在编写完每个功能后对其进行测试!使用HUnit和/或QuickCheck,只是为了确保这些东西都不会出现在最终代码中。]

于 2012-09-04T19:56:22.870 回答
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实例选择任何比较运算符来实现的函数。

于 2012-09-05T13:42:34.640 回答