如何在Applicatives上测试多态性函数?

时间:2016-01-21 19:21:42

标签: haskell applicative quickcheck

我只是written a function(对于Data.Sequence

traverseWithIndex :: Applicative f => (Int -> a -> f b) -> Seq a -> f (Seq b)

哪个应该服从

traverseWithIndex f = sequenceA . mapWithIndex f

值得庆幸的是,这是对mapWithIndex来源的直接机械修改,因此我非常有信心这是正确的。但是,在更复杂的情况下,需要进行彻底的测试。我试图编写一个QuickCheck属性来测试这个简单的属性。显然,我不能用每个Applicative仿函数试一试!当测试幺半群时,使用某种类型的自由幺半群(即有限列表)进行测试是很有意义的。所以在这里用free applicative functor测试一些仿函数似乎是明智的。有两个困难:

  1. 如何选择合适的基础仿函数?我可能想要一个不适用或者可以穿越或者任何东西的讨厌的东西,但是这样的事情似乎很难处理。

  2. 如何比较结果?它们中包含函数,因此它们没有Eq个实例。

2 个答案:

答案 0 :(得分:3)

  

显然,我无法在每个Applicative仿函数中试用它!

我提醒过这篇博文系列,我不会声称完全明白:

我记得从中吸取的教训是,你在野外看到的几乎所有应用函子都是这些更简单的构图,产品或(限制)副产品(并不是详尽无遗的):

  1. Const
  2. Identity
  3. (->)
  4. 因此,虽然您无法使用每个Applicative仿函数进行尝试,但您可以在QuickCheck属性中利用归纳参数来确信您的函数适用于大型归纳定义的族的算子。例如,您可以测试:

    • 您的功能正常运行" atomic"你选择的应用;
    • 如果您的功能适用于仿函数fg,则它适用于Compose f gProduct f gCoproduct f g
      

    如何比较结果?它们中包含函数,因此它们没有Eq个实例。

    好吧,我认为您可能需要查看函数相等的QuickCheck测试。上次我不得不沿着这些方向做一些事情,我去了Conal的checkers库,它有an EqProp class用于" [t] ypes的值可以测试相等,也许是通过随机抽样。"这应该已经给你一个想法 - 即使你没有函数的Eq实例,QuickCheck可能能够证明两个函数是不相等的。关键的是,这个实例存在:

    instance (Show a, Arbitrary a, EqProp b) => EqProp (a -> b)
    

    ...任何具有Eq实例的类型都有一个简单的EqProp实例(=-=) = (==)

    因此,在我看来,使用Coyoneda Something作为基础函子,并弄清楚如何将所有小函数连接在一起。

答案 1 :(得分:3)

这是部分(?)解决方案。我们要检查的主要方面是1)显然计算相同的值,2)效果以相同的顺序执行。我认为以下代码不言自明:

{-# LANGUAGE FlexibleInstances #-}
module Main where
import Control.Applicative
import Control.Applicative.Free
import Data.Foldable
import Data.Functor.Identity
import Test.QuickCheck
import Text.Show.Functions -- for Show instance for function types

data Fork a = F a | G a deriving (Eq, Show)

toIdentity :: Fork a -> Identity a
toIdentity (F a) = Identity a
toIdentity (G a) = Identity a

instance Functor Fork where
    fmap f (F a) = F (f a)
    fmap f (G a) = G (f a)

instance (Arbitrary a) => Arbitrary (Fork a) where
    arbitrary = elements [F,G] <*> arbitrary

instance (Arbitrary a) => Arbitrary (Ap Fork a) where
    arbitrary = oneof [Pure <$> arbitrary, 
                       Ap <$> (arbitrary :: Gen (Fork Int)) <*> arbitrary]

effectOrder :: Ap Fork a -> [Fork ()]
effectOrder (Pure _) = []
effectOrder (Ap x f) = fmap (const ()) x : effectOrder f

value :: Ap Fork a -> a
value = runIdentity . runAp toIdentity

checkApplicative :: (Eq a) => Ap Fork a -> Ap Fork a -> Bool
checkApplicative x y = effectOrder x == effectOrder y && value x == value y

succeedingExample = quickCheck (\f x -> checkApplicative 
    (traverse (f :: Int -> Ap Fork Int) (x :: [Int])) 
    (sequenceA (fmap f x)))

-- note reverse
failingExample = quickCheck (\f x -> checkApplicative 
    (traverse (f :: Int -> Ap Fork Int) (reverse x :: [Int])) 
    (sequenceA (fmap f x)))

-- instance just for example, could make a more informative one
instance Show (Ap Fork Int) where show _ = "<Ap>"

-- values match ...
betterSucceedingExample = quickCheck (\x -> 
    value (sequenceA (x :: [Ap Fork Int])) 
 == value (fmap reverse (sequenceA (reverse x))))

-- but effects don't.
betterFailingExample = quickCheck (\x -> checkApplicative 
    (sequenceA (x :: [Ap Fork Int])) 
    (fmap reverse (sequenceA (reverse x))))

输出如下:

*Main Text.Show.Functions> succeedingExample             
+++ OK, passed 100 tests.                                
*Main Text.Show.Functions> failingExample                
*** Failed! Falsifiable (after 3 tests and 2 shrinks):   
<function>                                               
[0,1]               
*Main Text.Show.Functions> betterSucceedingExample
+++ OK, passed 100 tests.
*Main Text.Show.Functions> betterFailingExample
*** Failed! Falsifiable (after 10 tests and 1 shrink):
[<Ap>,<Ap>]