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 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
<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
。
来自 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 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
<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
。