2017-04-14 4 views
2

Le StateT est Control.Monad.Trans.State.LazyComment écrire un test pour StateT en utilisant QuickCheck

La fonction à l'intérieur et m étant plus kinded rend difficile

{-# LANGUAGE FlexibleContexts #-} 
import Test.QuickCheck 

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) } 
instance (CoArbitrary s, Arbitrary s, Arbitrary a) => 
    Arbitrary (StateT s (Maybe) a) where -- doesn't quite work 
    arbitrary = undefined 

La raison pour laquelle je veux faire est parce que je veux pour vérifier en utilisant QuickCheck si l'instance applicative pour StateT que j'écris (pour la pratique) est correcte

Editer: ok, voici l'instance que je veux tester (soi-disant incorrect)

instance (Monad m) => Applicative (StateT s m) where 
    pure x = StateT (\s -> (\a -> (a, s)) <$> pure x) 
    StateT smfs <*> StateT smas = StateT $ \s -> liftA2 (\ (f, s) (a, _) -> (f a, s)) (smfs s) (smas s) 
+0

Cela ... ressemble à un problème XY. – Zeta

+0

@Zeta Je pense que tout le monde peut rechercher la solution comme StateT est dans la bibliothèque de base ... mais le point est, je veux le tester – pterodragon

+0

Je ne parle pas de * "réinventer la roue" * (par le façon, 'StateT' ce n'est pas dans' base'). Je parle de vous [poser des questions sur "X" (comment écrire une instance Arbitarry) mais en fait juste vouloir "Y" (comment tester mon instance applicative de StateT)] (https://meta.stackexchange.com/ questions/66377/quoi-est-le-xy-problème). – Zeta

Répondre

2

Votre question est vraiment intéressante. En effet, il serait très agréable de vérifier les lois fonctor/apllicative/monad en utilisant QuickCheck pour le transformateur monade StateT. Parce que c'est l'une des applications les plus utiles de QuickCheck. Mais l'écriture Arbitrary instance pour StateT n'est pas triviale. C'est possible. Mais en réalité, il n'y a pas de profit. Vous devriez utiliser la classe de type CoArbitrary de manière intelligente. Et aussi des extensions de couple. L'idée est décrite dans this blog post. Ayant CoArbitrary instance pour a -> b vous pouvez facilement créer Arbitrary instance pour StateT.

instance (CoArbitrary s 
     , Arbitrary s 
     , Arbitrary a 
     , Arbitrary (m a) 
     , Monad m 
     ) => Arbitrary (StateT s m a) 
    where 
    arbitrary = StateT <$> promote (\s -> fmap (,s) <$> arbitrary) 

Ensuite, vous pouvez générer des états:

ghci> (`runStateT` 3) <$> generate (arbitrary @(StateT Int Maybe Bool)) 
Just (True,3) 

Et vous pouvez même écrire propriété:

propStateFunctorId :: forall m s a . 
         (Arbitrary s 
         , Eq (m (a, s)) 
         , Show s 
         , Show (m (a, s)) 
         , Functor m 
        ) 
        => StateT s m a -> Property 
propStateFunctorId st = forAll arbitrary $ \s -> 
          runStateT (fmap id st) s === runStateT st s 

Mais vous ne pouvez pas exécuter cette propriété car elle nécessite instance Show pour StateT et vous Impossible d'écrire une instance sensée pour cela :(C'est comme ça que fonctionne QuickCheck.Il devrait imprimer un contre-exemple qui échoue.Connaissance du fait que 100 te sts ont été passés et 1 test échoué ne vous aide pas vraiment si vous ne savez pas lequel a échoué. Ce n'est pas un concours de programmation :)

ghci> quickCheck (propStateFunctorId @Maybe @Int @Bool) 
<interactive>:68:1: error: 
    • No instance for (Show (StateT Int Maybe Bool)) 
     arising from a use of ‘quickCheck’ 

Ainsi, au lieu de générer arbitraire StateT il est préférable de générer s et a puis vérifier les propriétés.

prop_StateTFunctorId :: forall s a . 
         (Arbitrary s 
         , Arbitrary a 
         , Eq a 
         , Eq s 
        ) 
        => s -> a -> Bool 
prop_StateTFunctorId s a = let st = pure a 
          in runStateT @_ @Maybe (fmap id st) s == runStateT st s 

ghci> quickCheck (prop_StateTFunctorId @Int @Bool) 
+++ OK, passed 100 tests. 

Cette approche n'exige pas la connaissance de certaines compétences de haut niveau.

+0

:) Je peux générer une instance de StateT en utilisant 'arbitrarary :: Gen (StateT Int Maybe Bool)'. Je ne comprends pas vraiment le signe "@" utilisé lors de la génération de l'instance StateT et dans 'prop_StateTFunctorId'. Est-ce une sorte d'extension de la langue? Je ne pouvais pas le chercher ... – pterodragon

+2

@pterodragon Oui, c'est l'extension '-XTypeApplications'. Vous pouvez trouver quelques informations dans ma réponse: http://stackoverflow.com/questions/42999199/how-can-i-get-the-type-of-a-polymorphic-function-for-a-specific-type-class -insta/43000393 # 43000393 Il ya un lien vers le guide de l'utilisateur haskell, vous pouvez lire plus là. Fonction très utile et agréable. Bref aperçu ici: http://kseo.github.io/posts/2017-01-08-visible-type-application-ghc8.html – Shersh

+0

Ma machine n'a pas ghc 8, je vais probablement vérifier plus tard :) – pterodragon