Haskell 任意类型类实例 return 类型

Haskell arbitrary typeclass instance return type

来自 Haskell 书中关于幺半群的章节,我正在为

编写快速检查测试
semigroupAssoc :: (Eq m, S.Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c =
    (a S.<> (b S.<> c)) == ((a S.<> b) S.<> c)

type IdentAssoc = Identity String -> Identity String -> Identity String -> Bool

,使用

调用
quickCheck (semigroupAssoc :: IdentAssoc)

这是任意类型类实例

instance (Arbitrary a) => Arbitrary (Identity a) where
    arbitrary = do
        a <- Test.QuickCheck.arbitrary
        return (Identity a)

我的问题是,在我的任意实例中,我可以 return (Identity a) 或只是 a。 (Identity a) 是正确的,但是当 运行 时,a 不会给出编译器错误并导致无限循环。这是为什么?

类型推断将 arbitrary 调用更改为指向我们现在正在定义的 相同 实例,从而导致无限循环。

instance (Arbitrary a) => Arbitrary (Identity a) where
   arbitrary = do
        x <- Test.QuickCheck.arbitrary  -- this calls arbitrary @ a
        return (Identity x)

instance (Arbitrary a) => Arbitrary (Identity a) where
   arbitrary = do
        x <- Test.QuickCheck.arbitrary  -- this calls arbitrary @ (Identity a)
        return x

每次我们调用 class 方法时,编译器都会推断调用哪个实例。

在前一种情况下,编译器推断出 x :: a(只有此类型会进行代码类型检查!),因此它会调用 arbitrary @ a

在后一种情况下,编译器会推断出 x :: Identity a(只有此类型会进行代码类型检查!),因此它会调用 arbitrary @ (Identity a),从而导致无限循环。

如果写成:

instance Arbitrary a => Arbitrary (Identity a) where
    <b>arbitrary</b> = do
        x <- <b>arbitrary</b>
        return x

(我将 a 重命名为 x 以减少混淆)。

然后 Haskell 将执行类型管道,它会派生出,因为你写 return xx 必须有类型 Identity a。我们很幸运,因为存在一个 arbitrary :: Arbitrary a => Gen a,我们刚刚定义的那个(在这里用粗体显示)。

所以这意味着我们已经构建了一个无限循环。例如在 Java 中,它看起来像:

public int foo(int x) {
    return foo(x);
}

我们当然可以定义这样的函数,而且我们检查类型是正确的。但是当然没有任何进展,因为我们一直在调用同一个函数,这个函数将再次调用该函数,一次又一次,...

如果你写:

instance Arbitrary a => Arbitrary (Identity a) where
    <b>arbitrary</b> = do
        x <- arbitrary
        return <b>Identity</b> x

Haskell 将导出 x 的类型为 a。我们又很幸运,因为我们已经定义了 Arbitrary a 应该成立,有些地方有一个 arbitrary :: Arbitrary a => Gen a 函数,但它是一个不同的函数(这就是为什么我没有在这里用粗体写),那个将生成我们包装在 Identity 构造函数中的值。

请注意,在 第一个示例中,我们甚至不必添加 Arbitrary a 约束:确实:

instance Arbitrary (Identity a) where
    <b>arbitrary</b> = do
        x <- <b>arbitrary</b>
        return x

这编译得很好,因为我们从来没有生成任意 a