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



在Haskell Book关于Monoids的章节中,我正在为编写快速检查测试

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)

我的问题是,在我的任意实例中,我可以返回(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

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

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

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

如果您将其写成:

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
returnIdentityx

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

相关内容

最新更新