public
Last active

QuickCheck'ing values in Template Haskell's Q monad.

  • Download Gist
gistfile1.hs
Haskell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
{-# LANGUAGE TemplateHaskell #-}
 
-- An example of testing TH-generated thingadoohickies.
--
-- Using the standard QuickCheck module, there is no direct way to test
-- values generated with Template Haskell functions, most of which end up
-- in the Q monad. This little writeup describes how to test those values
-- using the module Test.QuickCheck.Monadic.
--
-- For this example, you can ignore the doohickies being generated. I just
-- ripped them verbatim from the code that motivated this journey of
-- discovery. EXCEPT! One important thing about these doohickies is that
-- they don't use any of the name-mangling which is Q's raison d'être.
-- Were they to use it, it might be trickier (but not impossible?) to test
-- equality.
 
import Control.Monad ((=<<), liftM2)
import Language.Haskell.TH
import Test.QuickCheck
import Test.QuickCheck.Monadic
 
-- | One method of generating a thingadoohickie:
mkBoolFlagsOld :: [String] -> Q Dec
mkBoolFlagsOld flags =
dataD (cxt []) (mkName "BoolFlags") [] [dCon flags] [mkName "Show"]
where
dCon :: [String] -> ConQ
dCon = recC (mkName "BoolFlags") . map mkBoolRec
 
mkBoolRec :: String -> VarStrictTypeQ
mkBoolRec flag = do
bool <- [t|Bool|]
return (mkName flag, NotStrict, bool)
 
-- | Another method of generating the same(?) thingadoohickie:
mkBoolFlagsNew :: [String] -> Q Dec
mkBoolFlagsNew = mkFlags (mkName "BoolFlags") [t|Bool|]
where
mkFlags name typ flags = dataD (cxt []) name [] [dCon' name typ flags] [mkName "Show"]
 
dCon' name typ = recC name . map (mkTypRec typ)
 
mkTypRec typ flag = typ >>= (\t -> return $ (mkName flag, NotStrict, t))
 
-- So now there are two methods of generating the thing, both of type
-- [String] -> Q Dec.
--
-- I want to test the property that given the same input, these functions
-- return the same output.
--
-- Since their output is in the Q monad, I have to instruct QuickCheck on
-- how to set up the monadic 'environment' to ensure consistency between
-- the various runs. "Test.QuickCheck.Monadic" provides a framework for
-- doing this.
--
-- Now, there is more than one way to do this (many more, in fact), but I'll
-- start off with a property that looks similar to what I'd write in the
-- absence of monads:
 
propQ_same :: [String] -> Q Bool
propQ_same ss = (liftM2 (==)) (mkBoolFlagsOld ss) (mkBoolFlagsNew ss)
 
-- With no monads, I'd drop the liftM2 and be done. With monads, however, I
-- have to explicitly set up and run the proper environment. In my case,
-- the environment doesn't actually matter (see above), but to make the types
-- work I still need to stuff my property into the PropertyM monad. This is
-- accomplished with 'run':
 
propM_same_PROBLEM :: [String] -> PropertyM Q Bool
propM_same_PROBLEM = run . propQ_same
 
-- But wait! Now I have to think about running the PropertyM monad, and at
-- this point I have a problem.
--
-- There are three ways to run PropertyM. One is general, one is
-- specialized to m = IO, and one is specialized to m = ST. The ST version
-- is not the one I want. The general method seems appealing, but that
-- method needs to know how to 'unwrap' a monad. In the case of Q, such a
-- thing is not possible. That leaves the IO-specialized function, which is
-- called monadicIO and has type PropertyM IO a -> Property.
--
-- So let's see. I have PropertyM Q a, and I want PropertyM IO a. If I
-- could just convert Q to IO... and I can! The function to use is
--
-- runQ :: Quasi m => Q a -> m a.
--
-- There is a Quasi instance for IO!
--
-- Inserting runQ before run, I get:
 
propM_same :: [String] -> PropertyM IO Bool
-- ^ (Specializing to IO is not precisely necessary here, as it would be
-- inferred later, but I'll add it for clarity.)
propM_same = run . runQ . propQ_same
 
-- Finally, I use monadicIO to create a property that can be tested by
-- quickCheck. I.e., I create something that is an instance of Testable:
 
prop_same :: [String] -> Property
prop_same = monadicIO . propM_same
 
-- And the test!
 
main = quickCheck prop_same
 
-- So to summarize, *one* way to test properties in the Q monad is to convert
-- the Q to IO with 'runQ', stuff that result into PropertyM, and then run
-- the whole lot with 'monadicIO'.
--
-- Sources:
--
-- Template Haskell:
-- http://www.haskell.org/haskellwiki/Template_Haskell
--
-- QuickCheck for monadic values:
-- http://hackage.haskell.org/packages/archive/QuickCheck/latest/doc/html/Test-QuickCheck-Monadic.html
--
-- "Testing Monadic Code with QuickCheck":
-- http://www.cse.chalmers.se/~rjmh/Papers/QuickCheckST.ps

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.