如何编写关于函数属性的quickCheck?

时间:2018-10-25 02:51:05

标签: haskell quickcheck

我正在尝试做Haskell书中的Monooid练习之一(第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)

看来我必须在这种类型的实例上ArbitraryEq实例,但是如何为函数编写它们呢?

有一个similar question,在这个问题上,我们被要求为Combine编写Semigroup实例。

2 个答案:

答案 0 :(得分:6)

第一个完整的代码示例:

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这件事是关于什么的。如果ArbitraryFun a b有某些实例可用,则a有一个b实例。但是大多数时候我们都有那些。

可以显示类型为Fun a b的值,因此Fun a b具有显示实例,只要ab具有一个实例。我们可以使用applyFun提取函数。

要让QuickCheck充分利用这一点,我们需要提供一个Testable,可以在其中随机生成并显示所有参数位置。

因此,我们必须根据abFun a b来制定属性。

要将所有这些与Combine连接起来,我们提供了从Fun a bCombine a b的功能。

现在,我们陷入了另一个问题。我们无法比较函数,因此无法比较类型为Combine a b的值是否相等。因为我们已经在随机生成测试用例,所以为什么不只是随机生成点,以在这些点上测试功能是否相等。平等不是确定的事情,但是我们正在寻找可证伪的例子!这样对我们来说足够了。为此,我们提供了一个函数,将Combine a b类型的值“应用”到类型a的值,以获得类型b的值,希望可以将其进行比较平等。

答案 1 :(得分:3)

您可以使用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