HList-0.5.4.0: examples/Properties/LengthDependent.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
-- NOTE:
--
-- To be able to compile with ghc-7.6 functions like foo are sometimes
-- called
--
-- $(varE 'foo) because this prevents ghc-7.6 from failing to typecheck
-- the expression (which fails because the number of elements in the
-- supplied HList isn't known until Properties.LengthDependentSplice)
module Properties.LengthDependent where
#if MIN_VERSION_base(4,9,0)
import qualified Data.Kind as DK
#endif
import Data.HList.HSort (hMSortBy)
import Data.HList.Variant (eqVariant)
import Data.HList.Record (hZipRecord2)
import Data.HList.HList (hAppend')
import Data.HList.CommonMain
#if MIN_VERSION_template_haskell(2,17,0)
import Language.Haskell.TH.Lib.Internal hiding (doE)
import Language.Haskell.TH (Name, mkName, doE)
#else
import Language.Haskell.TH
#endif
import Test.QuickCheck
import Properties.Common
import Test.Hspec
import Control.Lens
import Data.List (sort,permutations)
import Data.Monoid
hlN :: Int -> ExpQ
hlN n = [| \proxy -> hSequence
$ $(varE 'hReplicate) $(hNatE n)
(arbitrary `asTypeOf` return proxy) |]
-- > $(rKN id n) (undefined :: t) :: Arbitrary t => Gen (HList [Record t1, Record t2, ... ])
--
-- where
-- t1 ~ '[Tagged 1 t, Tagged 2 t, Tagged 3 t, ... , Tagged n t]
-- t2 ~ '[Tagged 2 t, Tagged 1 t, Tagged 3 t, ... , Tagged n t]
-- tN ~ nth permutation of t1
rKN :: (forall a. [a] -> [a]) -- ^ take some subset of the permutations of 1 .. n
-> Int
-> ExpQ
rKN = rKN' (litT . numTyLit)
rKN' ::
(Integer -> TypeQ) -- ^ make the label
-> (forall a. [a] -> [a]) -- ^ take some subset of the permutations of 1 .. n
-> Int
-> ExpQ
rKN' mkLab takeK n = [| \proxy -> do
$(recs [| arbitrary `asTypeOf` return proxy |])
`asTypeOf` return $sig
|]
where sig = [| undefined |] `sigE` quantify [t| HList $(hListT rss) |]
ti :: Int -> Name
ti i = mkName ("t" ++ show i)
recs gen = doE $
[ bindS (varP (ti i)) gen | i <- [1 .. n] ] ++
[ noBindS
[| return $ $(hListE
[ [| unlabeled # $(hListE (map (varE . ti) is)) |]
| is <- takeK $ permutations [1 .. n] ])
|]
]
myForallT :: [Name] -> TypeQ -> TypeQ
#if MIN_VERSION_template_haskell(2,17,0)
myForallT ns = forallT [ plainInvisTV n inferredSpec | n <- ns ] (cxt [])
#else
myForallT ns = forallT (map plainTV ns) (cxt [])
#endif
quantify :: TypeQ -> TypeQ
quantify = myForallT [ mkName ("x" ++ show i) | i <- [1 .. n]]
rss :: [TypeQ]
rss = takeK $
#if MIN_VERSION_base(4,9,0)
[ [t| (Record :: [DK.Type] -> DK.Type) $(hListT (map taggedN ns)) |]
#else
[ [t| (Record :: [*] -> *) $(hListT (map taggedN ns)) |]
#endif
| ns <- permutations [1 .. fromIntegral n] ]
-- taggedN 1 == [t| Tagged 1 x1 |]
taggedN :: Integer -> TypeQ
taggedN i = [t| Tagged $(mkLab i) $(varT (mkName ("x"++show i))) |]
-- | > $(rN n) :: a -> Record [Tagged 1 a, Tagged 2 a, ... Tagged n a]
rN n = [| \proxy -> $(varE 'hHead) `fmap` $(rKN (take 1) n) proxy |]
-- | > $(rNstr n) :: a -> Record [Tagged "1" a, Tagged "2" a, ... Tagged n a]
rNstr n = [| \proxy -> $(varE 'hHead) `fmap` $(rKN' (litT . strTyLit . show) (take 1) n) proxy |]
vN :: Int -> ExpQ
vN n = [| \proxy -> do
let toV :: Gen (Record a) -> Variant a
toV = undefined
v <- arbitrary
return (v `asTypeOf` toV ($(rN n) proxy))
|]
-- specs for 1 HList of length >= 1
hl1 n1 = [| do
let -- | generate a HList of length nMax containing elements
-- selected from there
genHL proxy = $(hlN n1) proxy
it "hConcat/hAppend" $
property $ do
x <- genHL True
y <- genHL True
return $ conjoin [$(varE 'hConcat) ($(varE 'hBuild) x y) == hAppend x y,
$(varE 'hConcat) ($(varE 'hBuild) x y) == hAppend' x y,
$(varE 'hConcat) (hBuild x) == x]
it "partition" $
property $ do
x <- genHL True
return $ conjoin
[hPartitionEq (Proxy :: Proxy ConstTrue) (Proxy :: Proxy ()) x `eq` (x, HNil),
hPartitionEq (Proxy :: Proxy ConstFalse) (Proxy :: Proxy ()) x `eq` (HNil, x)]
it "listAsHList/hList2List" $ do
property $ do
x <- genHL True
return $ conjoin [
review listAsHList x `eq` hList2List x,
review listAsHList' x `eq` hList2List x]
it "read/show" $
property $ do
xs <- genHL True
return $ read (show xs) == xs
it "hLength/hReplicate" $
property $ do
xs <- genHL True
return $ hNat2Integral (hLength xs) == hNat2Integral $(hNatE n1)
it "hInits last id" $
property $ do
xs <- genHL True
return $ $(varE 'hLast) (hInits xs) == xs
it "hInits head empty" $
property $ do
xs <- genHL True
return $ hHead (hInits xs) == HNil
it "hTails head id" $
property $ do
xs <- genHL True
return $ hHead (hTails xs) == xs
it "hTails last empty" $
property $ do
xs <- genHL True
return $ $(varE 'hLast) (hTails xs) == HNil
it "hScanr equals scanr" $
property $ do
f <- arbitrary
a <- arbitrary
hl <- genHL True
return $ hList2List (hScanr (BinF f) a hl)
== scanr f a (hList2List hl)
it "hFoldr equals foldr" $
property $ do
f <- arbitrary
a <- arbitrary
hl <- genHL True
return $ hFoldr (BinF f) a hl == foldr f a (hList2List hl)
it "hFoldr1 equals foldr1" $
property $ do
f <- arbitrary
hl <- genHL True
return $ hFoldr1 (BinF f) hl == foldr1 f (hList2List hl)
it "hFoldl equals foldl" $
property $ do
f <- arbitrary
a <- arbitrary
hl <- genHL True
return $ hFoldl (BinF f) a hl == foldl f a (hList2List hl)
it "hSplitAt" $
property $ do
hl <- genHL True
let n = hLength hl
l = hList2List hl
-- hList2List doesn't like empty lists, and hMapOut id needs
-- annotations, so the following cases are easier to construct
-- than a direct comparison with splitAt
return $ conjoin
[ case hSplitAt hZero hl of
(hNil, hl') -> (hNil `eq` HNil) .&&. (hl' `eq` hl),
case $(varE 'hSplitAt) n hl of
(hl', hNil) -> (hNil `eq` HNil) .&&. (hl' `eq` hl),
$(varE 'hMap) (HSplitAtAppend hl) ($(varE 'hIterate) (hSucc n) HSuccF hZero) `eq` $(varE 'hReplicate) (hSucc n) hl ,
map (\n -> uncurry (++) $ splitAt n l) [0 .. length l] === replicate (length l+1) l
-- the equivalent list-version
]
it "hAppend empty is identity" $
property $ do
x <- genHL (BoolN True :: BoolN "x")
return $ all (== x) [$(varE 'hAppend) HNil x, $(varE 'hAppend) x HNil]
it "hReverse involution" $ do
property $ do
x <- genHL True
return $ x == $(varE 'hReverse) (hReverse x)
it "hReverse does nothing for ()" $
let xs = $(varE 'hReplicate) $(hNatE n1) ()
in xs `shouldBe` $(varE 'hReverse) xs
it "hInit == tail on reverse" $
property $ do
let hInitReference xs = hReverse (hTail (hReverse xs))
hl <- genHL True
return $ $(varE 'hInit) hl `eq` $(varE 'hInitReference) hl
it "hList2List/list2HList" $ property $ do
x <- genHL True
return $ list2HList (hList2List x) === Just x
it "hMap equals map" $ property $ do
f <- arbitrary
hl <- genHL True
return $ hList2List (hMap f hl) `eq` map (f :: Bool -> BoolN "f") (hList2List hl)
it "hZip" $ property $ do
x <- genHL (BoolN True :: BoolN "x")
y <- genHL (BoolN True :: BoolN "y")
return $ hList2List (hZip x y) `eq` hList2List x `zip` hList2List y
it "hZipRecord" $ property $ do
x <- $(rN n1) (BoolN True :: BoolN "x")
y <- $(rN n1) (BoolN True :: BoolN "y")
let r1 = hZip x y ^. unlabeled & hList2List
r2 = hZipRecord2 x y ^. unlabeled & hList2List
r_ = hList2List (x ^. unlabeled) `zip` hList2List (y ^. unlabeled)
return $ conjoin [
r1 `eq` r_,
r2 `eq` r_,
hUnzip (hZip x y) `eq` (x,y) ]
it "hZip/hUnZip" $ property $ do
x <- genHL (BoolN True :: BoolN "x")
y <- genHL (BoolN True :: BoolN "y")
return $ hUnzip (hZip x y) == (x,y)
it "hUnzip/hZip" $ property $ do
xy <- genHL (BoolN True :: BoolN "x", BoolN True :: BoolN "y")
let (x,y) = hUnzip xy
return $ xy `eq` hZip x y
it "hUnzip2/hZip2" $ property $ do
xy <- genHL (BoolN True :: BoolN "x", BoolN True :: BoolN "y")
let (x,y) = hUnzip2 xy
return $ xy `eq` hZip2 x y
-- XXX doesn't work with ghc-7.10.1
-- (should be fixed for 7.10.2)
it "hZip/hZip2" $ property $ do
x <- genHL (BoolN True :: BoolN "x")
y <- genHL (BoolN True :: BoolN "y")
return $ hZip x y `eq` hZip2 x y
-- lots of duplication, not sure if it's worth factoring out
it "HList monoid unit" $
property $ do
x <- genHL (BoolN True :: BoolN "x")
return $ conjoin
[ x === (x `mappend` mempty),
x === (mempty `mappend` x) ]
it "Record monoid unit" $
property $ do
x <- $(rN n1) (BoolN True :: BoolN "x")
return $ conjoin
[ x === (x `mappend` mempty),
x === (mempty `mappend` x) ]
it "Variant monoid unit" $
property $ do
x <- $(rN n1) (BoolN True :: BoolN "x")
return $ conjoin
[ x === (x `mappend` mempty),
x === (mempty `mappend` x) ]
-- lots of duplication, not sure if it's worth factoring out
it "HList monoid assoc" $
property $ do
x <- genHL (BoolN True :: BoolN "x")
y <- genHL (BoolN True :: BoolN "x")
z <- genHL (BoolN True :: BoolN "x")
return $ ((x `mappend` y) `mappend` z) `eq` (x `mappend` (y `mappend` z))
it "Record monoid assoc" $ property $ do
x <- $(rN n1) (BoolN True :: BoolN "x")
y <- $(rN n1) (BoolN True :: BoolN "x")
z <- $(rN n1) (BoolN True :: BoolN "x")
return $ ((x `mappend` y) `mappend` z) `eq` (x `mappend` (y `mappend` z))
it "Variant monoid assoc" $ property $ do
x <- $(vN n1) (BoolN True :: BoolN "x")
y <- $(vN n1) (BoolN True :: BoolN "x")
z <- $(vN n1) (BoolN True :: BoolN "x")
return $ ((x `mappend` y) `mappend` z) `eq` (x `mappend` (y `mappend` z))
it "Variant == /eqVariant" $ property $ do
x <- $(vN n1) (BoolN True :: BoolN "x")
y <- $(vN n1) (BoolN True :: BoolN "x")
return $ conjoin [ eqVariant x y == (x == y),
(x == y) == (y == x) ]
it "Variant ord" $ property $ do
x <- $(vN n1) (BoolN True :: BoolN "x")
y <- $(vN n1) (BoolN True :: BoolN "x")
z <- $(vN n1) (BoolN True :: BoolN "x")
let xyz = [x,y,z]
s:ss = map sort (permutations xyz)
return $ all (s ==) ss
#if __GLASGOW_HASKELL__ > 707 && __GLASGOW_HASKELL__ < 901
-- ghc-7.6 has no ordering for Nat (only for HNat)
it "hSort (the labels)" $ property $ do
x <- $(rN n1) True
let rx = x & from hListRecord %~ hReverse
-- rN generates a record that has labels in ascending order already
return $ conjoin [
x `eq` (x & from hListRecord %~ hSort),
x `eq` (rx & from hListRecord %~ hSort),
x `eq` (x & from hListRecord %~ hMSortBy (Proxy :: Proxy HLeFn)),
x `eq` (rx & from hListRecord %~ hMSortBy (Proxy :: Proxy HLeFn))
]
-- restrict to lists of length 4 (since then the number of permutations
-- is a manageable 24 not 120)
it "hSort permutations" $ property $ do
xs <- $(rKN id (min 4 n1)) True
return $ all (== hHead xs) (hMapOut HSortF xs)
#endif
it "hRenameLabel" $ property $ do
r <- $(rN n1) True
return $ conjoin
$(listE [ [| hRenameLabel $ln lx r .!. lx === r .!. $ln |]
| i <- [1 .. n1],
let ln = [| Label :: Label $(litT (numTyLit (fromIntegral i))) |]
])
#if __GLASGOW_HASKELL__ < 901
it "rearranged / hMapR" $ property $ do
r <- $(rN n1) True
let revR = r & from hListRecord %~ hReverse
asT :: x -> As x
asT _ = id
-- hMap works on the reversed list
return $ hMapR not r === (r & rearranged' . asT revR . unlabeled %~ hMap not)
#endif
it "hOccurs" $ property $ do
w <- arbitrary :: Gen (BoolN "w")
x <- genHL (BoolN True :: BoolN "x")
y <- genHL (BoolN True :: BoolN "y")
z <- genHL (BoolN True :: BoolN "z")
let xyz = hConcat (hBuild x y z)
hxyz = hEnd (hBuild (hHead x) (hHead y) (hHead z))
-- -XNoMonoLocalBinds on ghc <= 7.10.4 allowed
-- having one function
hM1 v = hOccursMany xyz === hList2List v
hM2 v = hOccursMany xyz === hList2List v
hM3 v = hOccursMany xyz === hList2List v
return $ conjoin
[ hM1 x, hM2 y, hM3 z,
hOccurs (hConcat (hBuild x (HCons w HNil) z)) === w,
hOccursOpt xyz === (Nothing `asTypeOf` Just w)
-- hProject hxyz === hBuild (hHead x) (hHead y)
]
|]
hl2 n1 n2 = [| do
it "splitVariant" $ property $ do
x <- $(vN (n1 + n2)) True
let testV :: forall n x yin yout.
(Eq (Variant x),
SplitVariant x yin yout,
HSplitAt n x yin yout,
ExtendsVariant yin x,
ExtendsVariant yout x) =>
Proxy n -> Variant x -> Bool
testV n v = case $(varE 'splitVariant) v of
Left a -> extendsVariant (a :: Variant yin) == v
Right a -> extendsVariant (a :: Variant yout) == v
return $ $(varE 'testV) $(hNatE n1) x
it "hAppend equals ++" $
property $ do
x <- $(hlN n1) True
y <- $(hlN n2) True
return $ hList2List (hAppend x y) === hList2List x ++ hList2List y
it "hTranspose involution" $ property $ do
x <- return (error "hTranspose involution") `asTypeOf` $(hlN n1) True
xx <- $(hlN n2) x
return $ $(varE 'hTranspose) ($(varE 'hTranspose) xx) === xx
it "leftUnion / unionSR" $
property $ do
x <- $(rN n1) True
y <- $(rN n2) True
let asL r = r ^. unlabeled . to hList2List
asLs (r1,r2) = (asL r1, asL r2)
merge xs ys = xs ++ drop (length xs) ys
mergeSym xs ys = (merge xs ys, merge ys xs)
eqSorted (a,b) (c,d) = sort a === sort c .&&. sort b === sort d
return $ conjoin [
asL (x .<++. y) === asL x `merge` asL y,
($(varE '(.<++.)) x x) === x,
($(varE '(.<++.)) y y) === y,
asLs (unionSR x y) `eqSorted` mergeSym (asL x) (asL y),
(x `unionSR` x) === (x,x),
(y `unionSR` y) === (y,y)]
|]
hl3 n1 n2 n3 = [| do
it "hAppend/hAppendList assoc" $
property $ do
x <- $(hlN n1) (BoolN True :: BoolN "x")
y <- $(hlN n2) (BoolN True :: BoolN "y")
z <- $(hlN n3) (BoolN True :: BoolN "z")
return $ conjoin
#if __GLASGOW_HASKELL__ < 707
[ $([| (x `hAppend` y) `hAppend` z |]) === $([| x `hAppend` (y `hAppend` z) |]),
$([| (x `hAppendList` y) `hAppendList` z|]) === $([| x `hAppendList` (y `hAppendList` z)|])
]
#else
[ ((x `hAppend` y) `hAppend` z) === (x `hAppend` (y `hAppend` z)),
((x `hAppendList` y) `hAppendList` z) === (x `hAppendList` (y `hAppendList` z))
]
#endif
|]