packages feed

ideas-0.7: src/Domain/Math/Numeric/Laws.hs

-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Algebraic laws, for testing purposes
--
-----------------------------------------------------------------------------
module Domain.Math.Numeric.Laws 
   ( numLaws, testNumLaws, testNumLawsWith
   , fracLaws, testFracLaws, testFracLawsWith
   ) where

import Common.TestSuite
import Test.QuickCheck

testNumLaws :: Num a => String -> Gen a -> TestSuite
testNumLaws = testNumLawsWith (==)

testNumLawsWith :: Num a => (a -> a -> Bool) -> String -> Gen a -> TestSuite
testNumLawsWith eq s g = suite ("Num instance for " ++ s) $
   mapM_ ($ g) (numLaws eq)

testFracLaws :: Fractional a => String -> Gen a -> TestSuite
testFracLaws = testFracLawsWith (==)

testFracLawsWith :: Fractional a => (a -> a -> Bool) -> String -> Gen a -> TestSuite
testFracLawsWith eq s g = suite ("Fractional instance for " ++ s) $ 
   mapM_ ($ g) (fracLaws eq)

numLaws :: Num a => (a -> a -> Bool) -> [Gen a -> TestSuite]
numLaws eq =
   [ law1 "plus zero left"     $ \a      ->      0+a === a
   , law1 "plus zero right"    $ \a      ->      a+0 === a
   , law2 "plus comm"          $ \a b    ->      a+b === b+a
   , law3 "plus trans"         $ \a b c  ->  a+(b+c) === (a+b)+c
   , law1 "negate zero"        $ \a      ->       -0 === 0        `asTypeOf` a
   , law1 "negate double"      $ \a      ->    -(-a) === a
   , law1 "minus zero left"    $ \a      ->      0-a === -a
   , law1 "minus zero right"   $ \a      ->      a-0 === a
   , law2 "negate plus"        $ \a b    ->   -(a+b) === -a-b
   , law2 "negate minus"       $ \a b    ->   -(a-b) === -a+b
   , law2 "plus negate"        $ \a b    ->   a+(-b) === a-b
   , law1 "times zero left"    $ \a      ->      0*a === 0
   , law1 "times zero right"   $ \a      ->      a*0 === 0
   , law1 "times one left"     $ \a      ->      1*a === a
   , law1 "times one right"    $ \a      ->      a*1 === a
   , law2 "times comm"         $ \a b    ->      a*b === b*a
   , law3 "times trans"        $ \a b c  ->  a*(b*c) === (a*b)*c
   , law2 "times negate left"  $ \a b    ->   (-a)*b === -(a*b)
   , law2 "times negate right" $ \a b    ->   a*(-b) === -(a*b)
   , law3 "times plus left"    $ \a b c  ->  (a+b)*c === a*c + b*c
   , law3 "times plus right"   $ \a b c  ->  a*(b+c) === a*b + a*c
   , law3 "times minus left"   $ \a b c  ->  (a-b)*c === a*c - b*c
   , law3 "times minus right"  $ \a b c  ->  a*(b-c) === a*b - a*c
   ]
 where
   infix 4 ===
   a === b = property (a `eq` b)

fracLaws :: Fractional a => (a -> a -> Bool) -> [Gen a -> TestSuite]
fracLaws eq =
   [ law3 "division numerator"   $ \a b c  ->      (a/b)/c === a/(b*c)          <| b/=0 && c/=0
   , law3 "division denominator" $ \a b c  ->      a/(b/c) === a*(c/b)          <| b/=0 && c/=0
   , law1 "zero numerator"       $ \a      ->          0/a === 0 <| a/=0
   , law1 "one numerator"        $ \a      ->          1/a === recip a          <| a/=0
   , law1 "one denominator"      $ \a      ->          a/1 === a
   , law1 "division is one"      $ \a      ->          a/a === 1                <| a/=0
   , law1 "recip double"         $ \a      ->            a === recip (recip a)  <| a/=0
   , law3 "times division left"  $ \a b c  ->      (a/b)*c === (a*c)/b          <| b/=0
   , law3 "times division right" $ \a b c  ->      a*(b/c) === (a*b)/c          <| c/=0
   , law3 "plus division left"   $ \a b c  ->      (a/b)+c === (a+c*b)/b        <| b/=0
   , law3 "plus division right"  $ \a b c  ->      a+(b/c) === (a*c+b)/c        <| c/=0
   , law3 "minus division left"  $ \a b c  ->      (a/b)-c === (a-c*b)/b        <| b/=0
   , law3 "minus division right" $ \a b c  ->      a-(b/c) === (a*c-b)/c        <| c/=0
   , law2 "negate numerator"     $ \a b    ->      a/(-b)  === -(a/b)           <| b/=0
   , law2 "negate denominator"   $ \a b    ->       (-a)/b === -(a/b)           <| b/=0
   , law2 "recip times"          $ \a b    ->  recip (a*b) === recip a*recip b  <| a/=0 && b/=0
   , law2 "recip division"       $ \a b    ->  recip (a/b) === b/a              <| a/=0 && b/=0
   ]
 where
   infix 4 ===
   a === b = property (a `eq` b)
   infix 1 <|
   p <| b = b ==> p

-- local helper-functions
law1 :: Show a => String -> (a -> Property) -> Gen a -> TestSuite
law1 s p g = addProperty s (make g id p)

law2 :: Show a => String -> (a -> a -> Property) -> Gen a -> TestSuite
law2 s p g = addProperty s (make g (make g id) p)

law3 :: Show a => String -> (a -> a -> a -> Property) -> Gen a -> TestSuite
law3 s p g = addProperty s (make g (make g (make g id)) p)

make :: Show a => Gen a -> (b -> Property) -> (a -> b) -> Property
make g c p = forAll g (c . p)