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

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-04 09:23:56
zakyggaps

How to

To automatically derive an instance of CoArbitrary, your data type should have an instance of Generic, which again can be automatically derived with some nice language extension:

{-# LANGUAGE DeriveGeneric #-}

import           GHC.Generics

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

However the most significant mistake in your program is you were testing against [] but not your own type by applicative [(Scss "b", Scss "a", Scss "c")]. here's the definition of applicative test bundle, details omitted:

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)
                      ]
                    )

In short, given four types m, a, b and c, this function will create a bunch of properties that m -- as an applicative functor -- should satisfy, and later you can test them with random a b c values generated by QuickCheck. [(Scss "b", Scss "a", Scss "c")] has type [(Validation String, Validation String, Validation String)] makes m ~ [].

So you should provide some value of type Validation e (a, b, c), or no value at all: You may have noticed the const right there in the definition of applicative, only the type of the argument matters:

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

After that you may run the test and get well-formatted result. But no, you shouldn't test an applicative in this way.


Why should not to

The test provided by checkers is far from sufficient. By the runtime-monomorphic nature of GHC and how it treats ambiguity, you have to supply four concrete, non-polymorphic type to run the test like Validation String (Int, Double, Char), and the test module will generate and test against only those four types , while your applicative functor should work with any type that meets the context.

IMO most of the polymorphic functions do not fit well into a unit test framework: it cannot be tested against all possible types, so one has to choose either just do some test anyway with hand chosen types, or do the test on a type that's general enough (like Free monad when your code requires an arbitrary monad, but usually "general enough" is not well defined in other contexts).

You'd better rigorously examine your implementation and prove all the laws satisfied for all the cases, either with pen and paper or with some proving engine like agda. Here is an example on Maybe that may help: Proving Composition Law for Maybe Applicative


EDIT: please read the comment. I'm not entirely understanding it but it means Integer is the "general enough" type for unit testing polymorphic functions. I found This blog article by Bartosz Milewski and its bibliography good resources for grabbing the idea of parametricity and free theorem.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!