如何编写函数属性的快速检查?
How to write quickCheck on properties of functions?
我正在尝试做 Haskell 书中的一个 Monoid 练习(第 15 章,"Monoid, Semigroup"),但我被卡住了。给出以下内容:
newtype Combine a b =
Combine { unCombine :: (a -> b) }
我应该为 Combine 编写 Monoid
实例。
我写了这样的东西:
instance (Semigroup b) => Semigroup (Combine a b) where
Combine { unCombine = f } <> Combine { unCombine = g } =
Combine { unCombine = \x -> f x <> g x }
instance (Monoid b) => Monoid (Combine a b) where
mempty = Combine { unCombine = \_ -> mempty }
mappend = (<>)
但我不知道如何为实例编写 quickCheck
。
这是我的尝试(未编译):
monoidLeftIdentity1 :: (Eq m, Monoid m) => m -> Bool
monoidLeftIdentity1 x = mappend mempty x == x
monoidRightIdentity1 :: (Eq m, Monoid m) => m -> Bool
monoidRightIdentity1 x = mappend x mempty == x
main :: IO ()
main = do
quickCheck (monoidLeftIdentity1 :: Combine Int (Sum Int) -> Bool)
quickCheck (monoidRightIdentity1 :: Combine Int (Sum Int) -> Bool)
看来我必须在这个类型上实例化 Arbitrary
和 Eq
,但是如何为函数编写它们?
有一个,在那个问题中,我们被要求为Combine编写Semigroup
实例。
您可以使用 Test.QuickCheck.Function
生成随机函数值,因此您应该能够编写如下内容来处理 Arbitrary
约束:
quickCheck (monoidLeftIdentity1 . Combine . apply :: Fun Int (Sum Int) -> Bool)
然而,对于 Eq
约束,您将无法比较函数值。我认为只需检查一些输入样本的逐点相等性就足够了,例如
funoidLeftIdentity1 :: (Monoid b, Eq b) => Fun a b -> a -> Bool
funoidLeftIdentity1 (Fn f) x = uncombine (Combine f <> mempty) x == uncombine mempty x
首先是一个完整的代码示例:
module Main where
import Test.QuickCheck
import Data.Monoid
newtype Combine a b = Combine { unCombine :: a -> b }
instance (Semigroup b) => Semigroup (Combine a b) where
a <> _ = a
-- (Combine f) <> (Combine g) = Combine $ \a -> (f a) <> (g a)
instance (Monoid b) => Monoid (Combine a b) where
mempty = Combine $ \_ -> mempty
monoidLeftIdentity :: (Eq m, Monoid m) => m -> Bool
monoidLeftIdentity m = mappend mempty m == m
monoidRightIdentity :: (Eq m, Monoid m) => m -> Bool
monoidRightIdentity m = mappend m mempty == m
monoidLeftIdentityF :: (Eq b, Monoid m) => (Fun a b -> m) -> (m -> a -> b) -> a -> Fun a b -> Bool
monoidLeftIdentityF wrap eval point candidate = eval (mappend mempty m) point == eval m point
where m = wrap candidate
monoidRightIdentityF :: (Eq b, Monoid m) => (Fun a b -> m) -> (m -> a -> b) -> a -> Fun a b -> Bool
monoidRightIdentityF wrap eval point candidate = eval (mappend m mempty) point == eval m point
where m = wrap candidate
main :: IO ()
main = do
quickCheck $ (monoidLeftIdentityF (Combine . applyFun) unCombine :: Int -> Fun Int (Sum Int) -> Bool)
quickCheck $ (monoidRightIdentityF (Combine . applyFun) unCombine :: Int -> Fun Int (Sum Int) -> Bool)
我们在这里做什么?
首先我们需要一种生成随机函数的方法。也就是这个Fun
的东西是关于什么的。如果 a
和 b
有特定实例可用,则 Fun a b
有一个 Arbitrary
实例。但大多数时候我们都有。
可以显示类型 Fun a b
的值,因此 Fun a b
有一个显示实例,前提是 a
和 b
有一个。我们可以用 applyFun
.
提取函数
为了让 QuickCheck 利用这一点,我们需要提供一个 Testable
可以随机生成和显示所有参数位置的地方。
所以我们必须根据 a
、b
和 Fun a b
来制定我们的属性。
为了将所有这些与 Combine
联系起来,我们提供了一个从 Fun a b
到 Combine a b
的函数。
现在我们遇到了另一个问题。我们不能比较函数,所以我们不能比较 Combine a b
类型的值是否相等。由于我们已经随机生成测试用例,为什么不也随机生成用于测试函数是否相等的点。相等性并不确定,但我们正在寻找可证伪的例子!所以这对我们来说已经足够了。为此,我们提供了一个函数 "apply" 类型 Combine a b
的值到类型 a
的值,以获得类型 b
的值,这有望成为比较平等。
我正在尝试做 Haskell 书中的一个 Monoid 练习(第 15 章,"Monoid, Semigroup"),但我被卡住了。给出以下内容:
newtype Combine a b =
Combine { unCombine :: (a -> b) }
我应该为 Combine 编写 Monoid
实例。
我写了这样的东西:
instance (Semigroup b) => Semigroup (Combine a b) where
Combine { unCombine = f } <> Combine { unCombine = g } =
Combine { unCombine = \x -> f x <> g x }
instance (Monoid b) => Monoid (Combine a b) where
mempty = Combine { unCombine = \_ -> mempty }
mappend = (<>)
但我不知道如何为实例编写 quickCheck
。
这是我的尝试(未编译):
monoidLeftIdentity1 :: (Eq m, Monoid m) => m -> Bool
monoidLeftIdentity1 x = mappend mempty x == x
monoidRightIdentity1 :: (Eq m, Monoid m) => m -> Bool
monoidRightIdentity1 x = mappend x mempty == x
main :: IO ()
main = do
quickCheck (monoidLeftIdentity1 :: Combine Int (Sum Int) -> Bool)
quickCheck (monoidRightIdentity1 :: Combine Int (Sum Int) -> Bool)
看来我必须在这个类型上实例化 Arbitrary
和 Eq
,但是如何为函数编写它们?
有一个Semigroup
实例。
您可以使用 Test.QuickCheck.Function
生成随机函数值,因此您应该能够编写如下内容来处理 Arbitrary
约束:
quickCheck (monoidLeftIdentity1 . Combine . apply :: Fun Int (Sum Int) -> Bool)
然而,对于 Eq
约束,您将无法比较函数值。我认为只需检查一些输入样本的逐点相等性就足够了,例如
funoidLeftIdentity1 :: (Monoid b, Eq b) => Fun a b -> a -> Bool
funoidLeftIdentity1 (Fn f) x = uncombine (Combine f <> mempty) x == uncombine mempty x
首先是一个完整的代码示例:
module Main where
import Test.QuickCheck
import Data.Monoid
newtype Combine a b = Combine { unCombine :: a -> b }
instance (Semigroup b) => Semigroup (Combine a b) where
a <> _ = a
-- (Combine f) <> (Combine g) = Combine $ \a -> (f a) <> (g a)
instance (Monoid b) => Monoid (Combine a b) where
mempty = Combine $ \_ -> mempty
monoidLeftIdentity :: (Eq m, Monoid m) => m -> Bool
monoidLeftIdentity m = mappend mempty m == m
monoidRightIdentity :: (Eq m, Monoid m) => m -> Bool
monoidRightIdentity m = mappend m mempty == m
monoidLeftIdentityF :: (Eq b, Monoid m) => (Fun a b -> m) -> (m -> a -> b) -> a -> Fun a b -> Bool
monoidLeftIdentityF wrap eval point candidate = eval (mappend mempty m) point == eval m point
where m = wrap candidate
monoidRightIdentityF :: (Eq b, Monoid m) => (Fun a b -> m) -> (m -> a -> b) -> a -> Fun a b -> Bool
monoidRightIdentityF wrap eval point candidate = eval (mappend m mempty) point == eval m point
where m = wrap candidate
main :: IO ()
main = do
quickCheck $ (monoidLeftIdentityF (Combine . applyFun) unCombine :: Int -> Fun Int (Sum Int) -> Bool)
quickCheck $ (monoidRightIdentityF (Combine . applyFun) unCombine :: Int -> Fun Int (Sum Int) -> Bool)
我们在这里做什么?
首先我们需要一种生成随机函数的方法。也就是这个Fun
的东西是关于什么的。如果 a
和 b
有特定实例可用,则 Fun a b
有一个 Arbitrary
实例。但大多数时候我们都有。
可以显示类型 Fun a b
的值,因此 Fun a b
有一个显示实例,前提是 a
和 b
有一个。我们可以用 applyFun
.
为了让 QuickCheck 利用这一点,我们需要提供一个 Testable
可以随机生成和显示所有参数位置的地方。
所以我们必须根据 a
、b
和 Fun a b
来制定我们的属性。
为了将所有这些与 Combine
联系起来,我们提供了一个从 Fun a b
到 Combine a b
的函数。
现在我们遇到了另一个问题。我们不能比较函数,所以我们不能比较 Combine a b
类型的值是否相等。由于我们已经随机生成测试用例,为什么不也随机生成用于测试函数是否相等的点。相等性并不确定,但我们正在寻找可证伪的例子!所以这对我们来说已经足够了。为此,我们提供了一个函数 "apply" 类型 Combine a b
的值到类型 a
的值,以获得类型 b
的值,这有望成为比较平等。