Haskell任意类型类实例返回类型

时间:2017-11-14 17:53:03

标签: haskell quickcheck

在关于Monoids的Haskell Book章节中,我正在为

编写quickcheck测试
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)

我的问题是,在我的任意实例中,我可以返回(身份a)或只是a。 (标识a)是正确的,但只是一个没有给出编译器错误,并在运行时导致无限循环。这是为什么?

2 个答案:

答案 0 :(得分:4)

类型推断会将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

每次调用类方法时,编译器都会推断出要调用的实例。

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

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

答案 1 :(得分:4)

如果你把它写成:

instance Arbitrary a => Arbitrary (Identity a) where
    arbitrary = do
        x <- arbitrary
        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
    arbitrary = do
        x <- arbitrary
        return Identity x

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

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

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

这个编译得很好,因为我们从不生成任意a