在关于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)是正确的,但只是一个没有给出编译器错误,并在运行时导致无限循环。这是为什么?
答案 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 x
,x
必须具有类型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
。