如何使用跳棋测试此应用实例? (没有 CoArbitrary 的实例(验证 e0 [Char]))

How do I test this applicative instance with checkers? (No instance for CoArbitrary (Validation e0 [Char]))

Checkers is a library for reusable QuickCheck properties, particularly for standard type classes

如何编写跳棋实例来测试我的验证应用实例是否有效?

import Test.QuickCheck
import Test.QuickCheck.Checkers
import Test.QuickCheck.Classes
import Control.Applicative
import Data.Monoid

data Validation e a =
  Error e
  | Scss a
  deriving (Eq, Show)

instance Functor (Validation e) where
  fmap _ (Error e) = Error e
  fmap f (Scss a) = Scss $ f a

instance Monoid e => Applicative (Validation e) where
  pure = Scss
  (<*>) (Scss f) (Scss a) = Scss $ f a
  (<*>) (Error g) (Scss a) = Error g
  (<*>) (Scss a) (Error g) = Error g
  (<*>) (Error a) (Error g) = Error $ mappend a g

instance (Arbitrary a, Arbitrary b) => Arbitrary (Validation a b) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    elements [Scss a, Error b]

instance (Eq a, Eq b) => EqProp (Validation a b) where (=-=) = eq

main :: IO ()
main = quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]

我想我快到了,但我收到如下错误:

chap17/Validation_applicative.hs:36:21: No instance for (CoArbitrary (Validation e0 [Char])) …
      arising from a use of ‘applicative’
    In the second argument of ‘($)’, namely
      ‘applicative [(Scss "b", Scss "a", Scss "c")]’
    In the expression:
      quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
    In an equation for ‘main’:
        main = quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
Compilation failed.

我试过像这样为验证添加 CoArbitrary 实例:

instance CoArbitrary (Validation a b)

但这会导致此错误消息:

chap17/Validation_applicative.hs:35:10: No instance for (GHC.Generics.Generic (Validation a b)) …
      arising from a use of ‘Test.QuickCheck.Arbitrary.$gdmcoarbitrary’
    In the expression: Test.QuickCheck.Arbitrary.$gdmcoarbitrary
    In an equation for ‘coarbitrary’:
        coarbitrary = Test.QuickCheck.Arbitrary.$gdmcoarbitrary
    In the instance declaration for ‘CoArbitrary (Validation a b)’
chap17/Validation_applicative.hs:38:21: No instance for (Eq e0) arising from a use of ‘applicative’ …
    The type variable ‘e0’ is ambiguous
    Note: there are several potential instances:
      instance Eq a => Eq (Const a b) -- Defined in ‘Control.Applicative’
      instance Eq a => Eq (ZipList a) -- Defined in ‘Control.Applicative’
      instance Eq a => Eq (Data.Complex.Complex a)
        -- Defined in ‘Data.Complex’
      ...plus 65 others
    In the second argument of ‘($)’, namely
      ‘applicative [(Scss "b", Scss "a", Scss "c")]’
    In the expression:
      quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
    In an equation for ‘main’:
        main = quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
Compilation failed.

如何

要自动派生 CoArbitrary 的实例,您的数据类型应该有一个 Generic 的实例,它也可以通过一些不错的语言扩展自动派生:

{-# LANGUAGE DeriveGeneric #-}

import           GHC.Generics

data Validation e a =
  Error e
  | Scss a
  deriving (Eq, Show, Generic)

然而,您程序中最严重的错误是您针对 [] 而不是 applicative [(Scss "b", Scss "a", Scss "c")] 自己的类型进行测试。这里是applicative测试包的定义,细节省略:

applicative :: forall m a b c.
               ( Applicative m
               , Arbitrary a, CoArbitrary a, Arbitrary b, Arbitrary (m a)
               , Arbitrary (m (b -> c)), Show (m (b -> c))
               , Arbitrary (m (a -> b)), Show (m (a -> b))
               , Show a, Show (m a)
               , EqProp (m a), EqProp (m b), EqProp (m c)
               ) =>
               m (a,b,c) -> TestBatch
applicative = const ( "applicative"
                    , [ ("identity"    , property identityP)
                      , ("composition" , property compositionP)
                      , ("homomorphism", property homomorphismP)
                      , ("interchange" , property interchangeP)
                      , ("functor"     , property functorP)
                      ]
                    )

简而言之,给定四种类型 mabc,此函数将创建一堆 m - - 作为应用函子 -- 应该满足,稍后您可以使用 QuickCheck 生成的随机 a b c 值来测试它们。 [(Scss "b", Scss "a", Scss "c")] 具有类型 [(Validation String, Validation String, Validation String)] 使得 m ~ [].

所以你应该提供一些 Validation e (a, b, c) 类型的值,或者根本不提供任何值:你可能已经注意到 applicative 定义中的 const,只有类型论点的重要性:

main :: IO ()
main = quickBatch $ applicative (undefined :: Validation String (Int, Double, Char))

之后您可以 运行 测试并获得格式正确的结果。但是不,你不应该以这种方式测试应用程序。


为什么不应该

checkers提供的测试远远不够。根据 GHC 的 运行 时间单态性质及其处理歧义的方式,您必须向 运行 测试 Validation String (Int, Double, Char) 提供四种具体的非多态类型,测试模块将仅针对这四种类型生成和测试,而您的应用函子应与满足上下文的任何类型一起使用。

IMO 大多数多态函数都不能很好地适合单元测试框架:它不能针对所有可能的类型进行测试,因此必须选择要么只使用手动选择的类型进行一些测试,要么进行测试在足够通用的类型上(例如当您的代码需要任意 monad 时的 Free monad,但通常 "general enough" 在其他上下文中定义不明确)。

你最好严格检查你的实现,并证明所有的法则都满足所有的情况,无论是用纸笔还是用像 agda 这样的证明引擎。这是 Maybe 上的一个示例,可能会有所帮助:Proving Composition Law for Maybe Applicative


编辑:请阅读评论。我并不完全理解它,但这意味着 Integer 是用于单元测试多态函数的 "general enough" 类型。我发现 This blog article by Bartosz Milewski 及其参考书目是掌握参数性和自由定理思想的好资源。