dimensions-2.1.1.0: test/Numeric/Dimensions/DimTest.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Numeric.Dimensions.DimTest (runTests) where
import Data.Constraint
import Data.Typeable
import Test.QuickCheck
import Numeric.Dimensions
import qualified Numeric.TypedList as TL
-- | Try inference of type-level natural values via term-level binary functions.
testBinaryOp :: forall (a :: Nat) (b :: Nat) (c :: Nat)
. (Word -> Word -> Word)
-> (Dim a -> Dim b -> Dim c)
-> Dim a -> Dim b -> Bool
testBinaryOp fTerm fType da db
| a <- dimVal da
, b <- dimVal db
, Dx dr <- someDimVal (fTerm a b)
-- pattern-match against @SomeDim@ to extract a type-level natural Dim.
, True <- fTerm a b == dimVal (fType da db)
-- compare the term-level function and the type-level function results
-- as regular word values.
, Just Dict <- sameDim dr (fType da db)
-- now the type system knows that @c ~ fType a b@ and
-- we can use ordinary equality function (which is @const True@).
= dr == fType da db
testBinaryOp _ _ _ _ = False
prop_plusDim :: Word -> Word -> Bool
prop_plusDim a b = case (someDimVal a, someDimVal b) of
(Dx da, Dx db) -> testBinaryOp (+) plusDim da db
prop_timesDim :: Word -> Word -> Bool
prop_timesDim a b = case (someDimVal a, someDimVal b) of
(Dx da, Dx db) -> testBinaryOp (*) timesDim da db
prop_powerDim :: Word -> Word -> Bool
prop_powerDim a b = case ( someDimVal a
, someDimVal b
) of
(Dx da, Dx db) -> testBinaryOp (^) powerDim da db
prop_minusDim :: Word -> Word -> Bool
prop_minusDim a' b'
| a <- max a' b'
, b <- min a' b'
, xda <- someDimVal a -- this is an unknown (Dim (XN 0))
, Dx (db@D :: Dim b) <- someDimVal b
, Just (Dx da) <- constrainDim @(XN b) xda -- here da >= db
= a - b == dimVal (minusDim da db)
prop_minusDim _ _ = False
-- | Matching against @Reverse@ pattern lets GHC know the reversion relation
-- at the type level.
-- That means the type system knows that reverse of reverse is the same list!
prop_reverseDims :: [Word] -> Property
prop_reverseDims xs
| SomeDims ds <- someDimsVal xs
= case ds of
Reverse rds -> case rds of
Reverse rrds -> ds === rrds
prop_concatDims :: [Word] -> [Word] -> Property
prop_concatDims xs ys
| SomeDims dxs <- someDimsVal xs
, SomeDims dys <- someDimsVal ys
= case TL.concat dxs dys of
dxsys -> listDims dxsys === xs ++ ys
-- | TODO: bring more evidence about list equality
prop_splitDims :: Word -> [Word] -> Property
prop_splitDims n xsys
| SomeDims dxsys <- someDimsVal xsys
, Dx dn <- someDimVal n
-- TODO: hmm, this is an incomplete pattern even though
-- Dn dn would not type check :(.
, (xs, ys) <- splitAt (fromIntegral n) xsys
= case TL.splitAt dn dxsys of
(dxs, dys) -> listDims dxs === xs
.&&. listDims dys === ys
.&&. case stripPrefixDims dxs dxsys >>= sameDims dys of
Nothing -> counterexample
("stripPrefixDims with " ++ show (dxs, dys, dxsys)) False
Just Dict -> property True
.&&. case stripSuffixDims dys dxsys >>= sameDims dxs of
Nothing -> counterexample
("stripSuffixDims with " ++ show (dxs, dys, dxsys)) False
Just Dict -> property True
-- .&&. dxsys === TL.concat dxs dys
#if __GLASGOW_HASKELL__ < 900
| otherwise = property False
#endif
prop_stripPrefixDims :: Int -> [Word] -> Property
prop_stripPrefixDims n l
| SomeDims xsys@Dims <- someDimsVal l
, SomeDims xs@Dims <- someDimsVal $ take (n `mod` (length l + 2)) l
, Just ys@Dims <- stripPrefixDims xs xsys
, Dict <- dimsAllEq xs
, Dict <- dimsAllTypeable xs
, Dict <- dimsAllTypeable xsys
, Just ys'@Dims <- TL.stripPrefix xs xsys
= case sameDims ys ys' of
Nothing -> counterexample
("stripPrefix[Dims] discrepancy " ++ show (ys, ys')) False
Just Dict -> property True
| otherwise = counterexample "stripPrefix of take does not work!" False
prop_stripSuffixDims :: Int -> [Word] -> Property
prop_stripSuffixDims n l
| SomeDims xsys@Dims <- someDimsVal l
, SomeDims ys@Dims <- someDimsVal $ drop (n `mod` (length l + 2)) l
, Just xs@Dims <- stripSuffixDims ys xsys
, Dict <- dimsAllEq ys
, Dict <- dimsAllTypeable ys
, Dict <- dimsAllTypeable xsys
, Just xs'@Dims <- TL.stripSuffix ys xsys
= case sameDims xs xs' of
Nothing -> counterexample
("stripSuffixDims[Dims] discrepancy " ++ show (xs, xs')) False
Just Dict -> property True
| otherwise = counterexample "stripSuffixDims of take does not work!" False
dimsAllEq :: Dims (ns :: [Nat]) -> Dict (All Eq (Map Dim ns))
dimsAllEq U = Dict
dimsAllEq (D :* ds) | Dict <- dimsAllEq ds = Dict
dimsAllTypeable :: Dims (ns :: [Nat]) -> Dict (All Typeable ns)
dimsAllTypeable U = Dict
dimsAllTypeable ((D :: Dim n) :* ds)
| Dict <- mapDict cls (Dict @(KnownDim n))
, Dict <- dimsAllTypeable ds = Dict
return []
runTests :: IO Bool
runTests = $quickCheckAll