mixed-types-num-0.3: src/Numeric/MixedTypes/MinMaxAbs.hs
{-# LANGUAGE TemplateHaskell #-}
{-|
Module : Numeric.MixedType.MinMaxAbs
Description : Bottom-up typed min, max and abs
Copyright : (c) Michal Konecny
License : BSD3
Maintainer : mikkonecny@gmail.com
Stability : experimental
Portability : portable
-}
module Numeric.MixedTypes.MinMaxAbs
(
-- * Minimum and maximum
CanMinMax, CanMinMaxAsymmetric(..), CanMinMaxThis, CanMinMaxSameType
, minimum, maximum
-- ** Tests
, specCanMinMax, specCanMinMaxNotMixed
-- * Absolute value
, CanAbs(..), CanAbsSameType
-- ** Tests
, specCanNegNum, specCanAbs
)
where
import Utils.TH.DeclForTypes
import Numeric.MixedTypes.PreludeHiding
import qualified Prelude as P
import Text.Printf
import qualified Data.List as List
import Test.Hspec
import Test.QuickCheck
-- import Numeric.CollectErrors
import Control.CollectErrors
import Numeric.MixedTypes.Literals
import Numeric.MixedTypes.Bool
import Numeric.MixedTypes.Eq
import Numeric.MixedTypes.Ord
{---- Min and max -----}
type CanMinMax t1 t2 =
(CanMinMaxAsymmetric t1 t2, CanMinMaxAsymmetric t2 t1,
MinMaxType t1 t2 ~ MinMaxType t2 t1)
{-|
A replacement for Prelude's `P.min` and `P.max`. If @t1 = t2@ and @Ord t1@,
then one can use the default implementation to mirror Prelude's @min@ and @max@.
-}
class CanMinMaxAsymmetric t1 t2 where
type MinMaxType t1 t2
type MinMaxType t1 t2 = t1 -- default
min :: t1 -> t2 -> MinMaxType t1 t2
max :: t1 -> t2 -> MinMaxType t1 t2
default min :: (MinMaxType t1 t2 ~ t1, t1~t2, P.Ord t1) => t1 -> t2 -> MinMaxType t1 t2
min = P.min
default max :: (MinMaxType t1 t2 ~ t1, t1~t2, P.Ord t1) => t1 -> t2 -> MinMaxType t1 t2
max = P.max
type CanMinMaxThis t1 t2 =
(CanMinMax t1 t2, MinMaxType t1 t2 ~ t1)
type CanMinMaxSameType t =
CanMinMaxThis t t
maximum :: (CanMinMaxSameType t) => [t] -> t
maximum (x:xs) = List.foldl' max x xs
maximum [] = error $ "maximum: empty list"
minimum :: (CanMinMaxSameType t) => [t] -> t
minimum (x:xs) = List.foldl' min x xs
minimum [] = error $ "minimum: empty list"
{-|
HSpec properties that each implementation of CanMinMax should satisfy.
-}
specCanMinMax ::
(Show t1, Show t2, Show t3, Show (MinMaxType t1 t2),
Show (MinMaxType t1 t1), Show (MinMaxType t2 t1),
Show (MinMaxType t1 (MinMaxType t2 t3)),
Show (MinMaxType (MinMaxType t1 t2) t3), Arbitrary t1,
Arbitrary t2, Arbitrary t3, CanTestCertainly (EqCompareType t1 t1),
CanTestCertainly (EqCompareType t2 t2),
CanTestCertainly (OrderCompareType (MinMaxType t1 t2) t2),
CanTestCertainly (OrderCompareType (MinMaxType t1 t2) t1),
CanTestCertainly (EqCompareType (MinMaxType t1 t1) t1),
CanTestCertainly
(EqCompareType (MinMaxType t1 t2) (MinMaxType t2 t1)),
CanTestCertainly (EqCompareType t3 t3),
CanTestCertainly
(EqCompareType
(MinMaxType t1 (MinMaxType t2 t3))
(MinMaxType (MinMaxType t1 t2) t3)),
HasEqAsymmetric t1 t1, HasEqAsymmetric t2 t2,
HasEqAsymmetric t3 t3,
HasEqAsymmetric (MinMaxType t1 t2) (MinMaxType t2 t1),
HasEqAsymmetric (MinMaxType t1 t1) t1,
HasEqAsymmetric
(MinMaxType t1 (MinMaxType t2 t3))
(MinMaxType (MinMaxType t1 t2) t3),
HasOrderAsymmetric (MinMaxType t1 t2) t1,
HasOrderAsymmetric (MinMaxType t1 t2) t2,
CanMinMaxAsymmetric t1 t1, CanMinMaxAsymmetric t1 t2,
CanMinMaxAsymmetric t1 (MinMaxType t2 t3),
CanMinMaxAsymmetric t2 t1, CanMinMaxAsymmetric t2 t3,
CanMinMaxAsymmetric (MinMaxType t1 t2) t3)
=>
T t1 -> T t2 -> T t3 -> Spec
specCanMinMax (T typeName1 :: T t1) (T typeName2 :: T t2) (T typeName3 :: T t3) =
describe (printf "CanMinMax %s %s, CanMinMax %s %s" typeName1 typeName2 typeName2 typeName3) $ do
it "`min` is not larger than its arguments" $ do
property $ \ (x :: t1) (y :: t2) ->
(x ?==? x) && (y ?==? y) ==> -- avoid NaN
let m = x `min` y in (m ?<=?$ y) .&&. (m ?<=?$ x)
it "`max` is not smaller than its arguments" $ do
property $ \ (x :: t1) (y :: t2) ->
(x ?==? x) && (y ?==? y) ==> -- avoid NaN
let m = x `max` y in (m ?>=?$ y) .&&. (m ?>=?$ x)
it "has idempotent `min`" $ do
property $ \ (x :: t1) ->
(x ?==? x) ==> -- avoid NaN
(x `min` x) ?==?$ x
it "has idempotent `max`" $ do
property $ \ (x :: t1) ->
(x ?==? x) ==> -- avoid NaN
(x `max` x) ?==?$ x
it "has commutative `min`" $ do
property $ \ (x :: t1) (y :: t2) ->
(x ?==? x) && (y ?==? y) ==> -- avoid NaN
(x `min` y) ?==?$ (y `min` x)
it "has commutative `max`" $ do
property $ \ (x :: t1) (y :: t2) ->
(x ?==? x) && (y ?==? y) ==> -- avoid NaN
(x `max` y) ?==?$ (y `max` x)
it "has associative `min`" $ do
property $ \ (x :: t1) (y :: t2) (z :: t3) ->
(x ?==? x) && (y ?==? y) && (z ?==? z) ==> -- avoid NaN
(x `min` (y `min` z)) ?==?$ ((x `min` y) `min` z)
it "has associative `max`" $ do
property $ \ (x :: t1) (y :: t2) (z :: t3) ->
(x ?==? x) && (y ?==? y) && (z ?==? z) ==> -- avoid NaN
(x `max` (y `max` z)) ?==?$ ((x `max` y) `max` z)
where
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)
(?>=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?>=?$) = printArgsIfFails2 "?>=?" (?>=?)
(?<=?$) :: (HasOrderCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?<=?$) = printArgsIfFails2 "?<=?" (?<=?)
--
{-|
HSpec properties that each implementation of CanMinMax should satisfy.
-}
specCanMinMaxNotMixed ::
(Show t, Show (MinMaxType t t),
Show (MinMaxType t (MinMaxType t t)),
Show (MinMaxType (MinMaxType t t) t), Arbitrary t,
CanTestCertainly (EqCompareType t t),
CanTestCertainly (OrderCompareType (MinMaxType t t) t),
CanTestCertainly (EqCompareType (MinMaxType t t) t),
CanTestCertainly
(EqCompareType (MinMaxType t t) (MinMaxType t t)),
CanTestCertainly
(EqCompareType
(MinMaxType t (MinMaxType t t))
(MinMaxType (MinMaxType t t) t)),
HasEqAsymmetric t t, HasEqAsymmetric (MinMaxType t t) t,
HasEqAsymmetric (MinMaxType t t) (MinMaxType t t),
HasEqAsymmetric
(MinMaxType t (MinMaxType t t))
(MinMaxType (MinMaxType t t) t),
HasOrderAsymmetric (MinMaxType t t) t,
CanMinMaxAsymmetric t t,
CanMinMaxAsymmetric t (MinMaxType t t),
CanMinMaxAsymmetric (MinMaxType t t) t)
=>
T t -> Spec
specCanMinMaxNotMixed t = specCanMinMax t t t
instance CanMinMaxAsymmetric Int Int
instance CanMinMaxAsymmetric Integer Integer
instance CanMinMaxAsymmetric Rational Rational
instance CanMinMaxAsymmetric Double Double
instance CanMinMaxAsymmetric Int Integer where
type MinMaxType Int Integer = Integer
min = convertFirst min
max = convertFirst max
instance CanMinMaxAsymmetric Integer Int where
type MinMaxType Integer Int = Integer
min = convertSecond min
max = convertSecond max
instance CanMinMaxAsymmetric Int Rational where
type MinMaxType Int Rational = Rational
min = convertFirst min
max = convertFirst max
instance CanMinMaxAsymmetric Rational Int where
type MinMaxType Rational Int = Rational
min = convertSecond min
max = convertSecond max
instance CanMinMaxAsymmetric Integer Rational where
type MinMaxType Integer Rational = Rational
min = convertFirst min
max = convertFirst max
instance CanMinMaxAsymmetric Rational Integer where
type MinMaxType Rational Integer = Rational
min = convertSecond min
max = convertSecond max
instance (CanMinMaxAsymmetric a b) => CanMinMaxAsymmetric [a] [b] where
type MinMaxType [a] [b] = [MinMaxType a b]
min (x:xs) (y:ys) = (min x y) : (min xs ys)
min _ _ = []
max (x:xs) (y:ys) = (max x y) : (max xs ys)
max _ _ = []
instance (CanMinMaxAsymmetric a b) => CanMinMaxAsymmetric (Maybe a) (Maybe b) where
type MinMaxType (Maybe a) (Maybe b) = Maybe (MinMaxType a b)
min (Just x) (Just y) = Just (min x y)
min _ _ = Nothing
max (Just x) (Just y) = Just (max x y)
max _ _ = Nothing
instance
(CanMinMaxAsymmetric a b
, CanEnsureCE es a, CanEnsureCE es b
, CanEnsureCE es (MinMaxType a b)
, SuitableForCE es)
=>
CanMinMaxAsymmetric (CollectErrors es a) (CollectErrors es b)
where
type MinMaxType (CollectErrors es a) (CollectErrors es b) =
EnsureCE es (MinMaxType a b)
min = lift2CE min
max = lift2CE max
$(declForTypes
[[t| Integer |], [t| Int |], [t| Rational |], [t| Double |]]
(\ t -> [d|
instance
(CanMinMaxAsymmetric $t b
, CanEnsureCE es b
, CanEnsureCE es (MinMaxType $t b)
, SuitableForCE es)
=>
CanMinMaxAsymmetric $t (CollectErrors es b)
where
type MinMaxType $t (CollectErrors es b) =
EnsureCE es (MinMaxType $t b)
min = lift2TLCE min
max = lift2TLCE max
instance
(CanMinMaxAsymmetric a $t
, CanEnsureCE es a
, CanEnsureCE es (MinMaxType a $t)
, SuitableForCE es)
=>
CanMinMaxAsymmetric (CollectErrors es a) $t
where
type MinMaxType (CollectErrors es a) $t =
EnsureCE es (MinMaxType a $t)
min = lift2TCE min
max = lift2TCE max
|]))
{-| Compound type constraint useful for test definition. -}
type CanNegX t =
(CanNeg t, Show t, Arbitrary t, Show (NegType t))
{---- numeric negation tests and instances -----}
{-|
HSpec properties that each numeric implementation of CanNeg should satisfy.
-}
specCanNegNum ::
(CanNegX t, CanNegX (NegType t),
HasEqCertainly t (NegType (NegType t)),
ConvertibleExactly Integer t,
HasEqCertainly t t,
HasEqCertainly t (NegType t),
CanTestPosNeg t,
CanTestPosNeg (NegType t)
)
=>
T t -> Spec
specCanNegNum (T typeName :: T t) =
describe (printf "CanNeg %s" typeName) $ do
it "ignores double negation" $ do
property $ \ (x :: t) ->
(x ?==? x) ==> -- avoid NaN
(negate (negate x)) ?==?$ x
it "takes 0 to 0" $ do
let z = convertExactly 0 :: t in negate z ?==? z
it "takes positive to negative" $ do
property $ \ (x :: t) ->
(isCertainlyPositive x) ==> (isCertainlyNegative (negate x))
it "takes negative to positive" $ do
property $ \ (x :: t) ->
(isCertainlyNegative x) ==> (isCertainlyPositive (negate x))
where
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)
instance CanNeg Int where negate = P.negate
instance CanNeg Integer where negate = P.negate
instance CanNeg Rational where negate = P.negate
instance CanNeg Double where negate = P.negate
{---- abs -----}
{-|
A replacement for Prelude's `P.abs`. If @Num t@,
then one can use the default implementation to mirror Prelude's @abs@.
-}
class CanAbs t where
type AbsType t
type AbsType t = t -- default
abs :: t -> AbsType t
default abs :: (AbsType t ~ t, P.Num t) => t -> AbsType t
abs = P.abs
type CanAbsSameType t = (CanAbs t, AbsType t ~ t)
instance CanAbs Int
instance CanAbs Integer
instance CanAbs Rational
instance CanAbs Double
instance
(CanAbs a
, CanEnsureCE es a
, CanEnsureCE es (AbsType a)
, SuitableForCE es)
=>
CanAbs (CollectErrors es a)
where
type AbsType (CollectErrors es a) = EnsureCE es (AbsType a)
abs = lift1CE abs
type CanAbsX t =
(CanAbs t,
CanNegSameType t,
CanTestPosNeg t,
CanTestPosNeg (AbsType t),
HasEqCertainly t t,
HasEqCertainly t (AbsType t),
Show t, Arbitrary t, Show (AbsType t))
{-|
HSpec properties that each implementation of CanAbs should satisfy.
-}
specCanAbs ::
(CanAbsX t, CanAbsX (AbsType t))
=>
T t -> Spec
specCanAbs (T typeName :: T t) =
describe (printf "CanAbs %s" typeName) $ do
it "is idempotent" $ do
property $ \ (x :: t) ->
(x ?==? x) ==> -- avoid NaN
(abs (abs x)) ?==?$ (abs x)
it "is identity on non-negative arguments" $ do
property $ \ (x :: t) ->
isCertainlyNonNegative x ==> x ?==?$ (abs x)
it "is negation on non-positive arguments" $ do
property $ \ (x :: t) ->
isCertainlyNonPositive x ==> (negate x) ?==?$ (abs x)
it "does not give negative results" $ do
property $ \ (x :: t) -> not $ isCertainlyNegative (abs x)
where
(?==?$) :: (HasEqCertainlyAsymmetric a b, Show a, Show b) => a -> b -> Property
(?==?$) = printArgsIfFails2 "?==?" (?==?)