{-# 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 "?==?" (?==?)