Testing of Quantity
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
Quantity
consists of a numerical type such as Double
and a value-level dimension. Let’s assume Haskell’s implementation of numbers abide the laws. We also know the value-level dimensions abide the laws since since we tested them in a previous section. So what’s there to test now? Well, it’s still useful to test the top-level construct. And error may arise when combing different parts.
So let’s test some things. Due to the type-level dimensions it’s impossible to easily and readably design test for all combinations of dimensions. Hence it’ll have to suffice with only some combinations.
Generating arbitrary quantities
First we need an Arbitrary
instance for Quantity d val
. For d
we’ll mostly use One
and for val
we’ll exclusively use Double
.
A generator for an arbitrary dimension.
genQuantity :: Quantity d Double -> Gen (Q d)
genQuantity quantity = do
value <- arbitrary
return (value # quantity)
And now we make Arbitrary
instances of arbitrary selected dimensions in the Quantity
s.
Testing arithmetic properties
On regular numbers, and hence too on quantites with their dimensions, a bunch of properties should hold. The things we test here are
- Addition commutative
- Addition associative
- Zero is identity for addition
- Multiplication commutative
- Multiplication associative
- One is identity for multiplication
- Addition distributes over multiplication
- Subtraction and addition cancel each other out
- Division and multiplication cancel each other out
- Pythagoran trigonometric identity
Let’s start!
We could write the type signatures in a general way like
But we won’t do that since QuickCheck needs concrete types in order to work. So we would have to do a bunch of specialization anyway. And even if we begin with a general signature, we can’t cover all cases since there are infinitly many dimensions.
Instead we’ll pick some arbitrary dimensions that have an Arbitrary
instance.
-- a + (b + c) = (a + b) + c
prop_addAss :: Q Mass -> Q Mass -> Q Mass -> Bool
prop_addAss a b c = a +# (b +# c) ~= (a +# b) +# c
-- a * (b * c) = (a * b) * c
prop_mulAss :: Q Time -> Q Length -> Q Mass -> Bool
prop_mulAss a b c = a *# (b *# c) ~=
(a *# b) *# c
-- a * (b + c) = a * b + a * c
prop_addDistOverMul :: Q Length -> Q Mass -> Q Mass -> Bool
prop_addDistOverMul a b c = a *# (b +# c) ~=
a *# b +# a *# c
-- (a + b) - b = a
prop_addSubCancel :: Q Length -> Q Length -> Bool
prop_addSubCancel a b = (a +# b) -# b ~= a