packages feed

mixed-types-num 0.5.0.0 → 0.5.0.1

raw patch · 6 files changed

+281/−255 lines, 6 filesdep ~collect-errorsPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

Dependency ranges changed: collect-errors

API changes (from Hackage documentation)

+ MixedTypesNumPrelude: (~!) :: Show es => CollectErrors es p -> p
+ MixedTypesNumPrelude: cn :: v -> CN v
+ MixedTypesNumPrelude: type CN = CollectErrors NumErrors
+ MixedTypesNumPrelude: unCN :: CN p -> p

Files

README.md view
@@ -3,8 +3,14 @@ This package provides a version of Prelude where unary and binary operations such as `not`, `+`, `==` have their result type derived from the parameter type(s)-and thus supports mixed-type arithmetic and comparisons.+and thus supports mixed-type arithmetic and comparisons such as: +    > a = [1..10]; b = [1..11]+    > length a > 2^((length b)/3)+    {?(prec 36): CertainFalse}+<!-- > (length a) `mod` 0.5+TODO -->+ Partial operations such as division, sqrt and power do not throw exceptions even when errors such as division by zero occur.  Instead, these errors are propagated bottom-up in@@ -15,6 +21,258 @@ Certain aspects are specifically tailored for interval or exact real arithmetics, including three-valued numerical comparisons and distinguishing potential and certain errors. -See module [MixedTypesNumPrelude](https://hackage.haskell.org/package/mixed-types-num/docs/MixedTypesNumPrelude.html) for further documentation.+## Generated documentation -[Hackage page including Haddock](https://hackage.haskell.org/package/mixed-types-num)+See the [Hackage page](https://hackage.haskell.org/package/mixed-types-num).++## Feature highlights++To replicate the examples included below, start ghci as follows:++    $ stack ghci mixed-types-num:lib+    ...> :add MixedTypesNumPrelude++### Main idea++Literals have a fixed type:++    ...> :t 1+    ... Integer++    ...> :t 1.0+    ... Rational+    +    ...> 1 :: Rational+    ... Couldn't match type ‘Integer’ with ‘GHC.Real.Ratio Integer’ ...++Operations permit operands of mixed types, types inferred bottom-up:++    ...> :t 1.5 + 1+    ... :: Rational++    ...> :t 1.5 * (length [[]])+    ... :: Rational++### Dealing with numerical errors++To avoid runtime exceptions, it is recommended to use the CN error-collecting wrapper from package collect-errors:++    ...> :t let n = cn 1 in n/(n-1)+    ... :: CN Rational++`CN` is a synonym for `CollectErrors NumErrors Rational` as defined in package [collect-errors](https://hackage.haskell.org/package/collect-errors) module [Numeric.CollectErrors](https://hackage.haskell.org/package/collect-errors).+The `CN` wrapper indicates that integer division can fail for some values:++    ...> let n = cn 1 in n/(n-1)+    {{ERROR: division by 0}}++Note that the error printed above is not an exception, but a special value.++All arithmetic operations have been extended so that it is possible to have expressions that operate exclusively on CN-wrapped types:++    ...> f (n :: CN Integer) = 1/(1/(n-1) + 1/n) :: CN Rational+    ...> f (cn 0)+    {{ERROR: division by 0}}+    ...> f (cn 1)+    {{ERROR: division by 0}}+    ...> f (cn 2)+    2 % 3++The function `hasError` from module [Numeric.CollectErrors](https://hackage.haskell.org/package/collect-errors) can be used to check whether any error occurred:++    ...> import qualified Numeric.CollectErrors as CN+    ... CN> CN.hasError (cn 1/0)+    True++    ...> CN.hasError (cn 1/1)+    False++To extract a value from the `CN` wrapper, one can use function `withErrorOrValue`:++    ...> CN.withErrorOrValue (const 0.0) id (cn 1/2)+    1 % 2++The following examples require also package [aern2-real](https://github.com/michalkonecny/aern2).+To get access to this via stack, you can start ghci eg as follows:++    $ stack ghci aern2-real:lib+    ...> :add AERN2.Real +    AERN2.Real> import MixedTypesNumPrelude++    ...> :t pi+    ...  :: CReal++    ...> :t sqrt 2+    ...  :: CReal++Harmless potential errors can be ignored using `unCN`:++    ...> sqrt (pi-pi) ? (prec 100)+    [0.0000000000000000006133173666733496325755399890... ± ~6.1332e-19 ~2^(-60)]{{POTENTIAL ERROR: out of domain: negative sqrt argument}}++    ...> unCN $ sqrt (pi-pi) ? (prec 100)+    [0.0000000000000000006133173666733496325755399890... ± ~6.1332e-19 ~2^(-60)]++If used unsafely, `unCN` will cause an exception:++    ...> unCN $ sqrt (-1) ? (prec 100)+    *** Exception: CollectErrors: {ERROR: out of domain: negative sqrt argument}++When an error is present (which can be checked using `hasError`), the function `CN.hasCertainError` can be used to further distinguish cases where the error is certain or potential:++    ...> import qualified Numeric.CollectErrors as CN+    ...> CN.hasCertainError (sqrt (-1) ? (prec 100))+    True++    ...> CN.hasCertainError (sqrt (pi-pi) ? (prec 100))+    False++### The generalised power operator++    ...> :t 2^(-2)+    ... :: Rational++    ...> :t 2^2+    ... :: Rational++    ...> :t round (2^2)+    ... :: Integer++    ...> :t (double 2)^(1/2)+    ... :: Double++The following example require also package [aern2-real](https://github.com/michalkonecny/aern2).++    ...> :t 2^(1/2)+    ... :: CReal++### Undecided comparisons++Comparisons involving intervals are undecided when the intervals overlap:++    > pi10 = pi ? (bits 10)+    > pi10+    [3.1416015625 ± ~9.7656e-4 ~2^(-10)]++    > pi10 > 0+    CertainTrue++    > pi10 == pi10+    TrueOrFalse++The above equality cannot be decided since `pi10` is not a single number but a set of numbers spanning the interval and the comparison operator cannot tell if the two operands sets represent the same number or a different number.++Comparison involving real numbers are semi-decidable.  The result of such a comparison is a lazy Kleenean, ie an infinite sequence of Kleeneans.+Please see package [aern2-real](https://github.com/michalkonecny/aern2) for further details.++### Fuzzy if-then-else++This package generalises the Haskell if-then-else statement so that it admits Kleenean and lazy Kleenean conditions:++    ...> abs1 x = max 0 (if x < 0 then -x else x)+    ...> abs1 (pi10 - pi10)+    [0.0009765625 ± ~9.7656e-4 ~2^(-10)]++Although the condition `x < 0` cannot be decided for the interval+`pi10-pi10 = [0 ± ~1.9531e-3 ~2^(-9)]`, the if-then-else statement is resolved by computing both branches and unifying the resulting intervals.  This makes sense only if both branches compute the same number whenever the condition cannot be decided, ie when `x = 0` in this case, making the function continuous.++If we try to define a discontinuous function this way, we get an error as soon as it is detected:++    ...> bad1 x = if x < 0 then 1-x else x+    ...> bad1 (pi10 - pi10)+    [0.5 ± ~0.5020 ~2^(-1)]{{ERROR: numeric error: union of enclosures: not enclosing the same value}}++The generalised if-then-else works also for real numbers with lazy Kleenean comparisons:++    ...> abs1 (pi - pi)+    {?(prec 36): [0.000000000014551915228366851806640625 ± ~1.4552e-11 ~2^(-36)]}++## Type classes++Mixed-type arithmetic operations are provided via multi-parameter type classes+and the result type is given by associated+type families. For example:++```Haskell+(+) :: (CanAddAsymmetric t1 t2) => t1 -> t2 -> AddType t1 t2+```++The constraint `CanAdd t1 t2` is a shortcut for both+`CanAddAsymmetric t1 t2` and `CanAddAsymmetric t2 t1`.++For convenience there are other aggregate type constraints such as+`CanAddThis t1 t2`, which implies that the result is of type `t1`,+and `CanAddSameType t`, which is a shortcut for `CanAddThis t t`.++Notably, there are convenience classes `Ring` and `Field` as well as `OrderedRing` and `OrderedField`.++For types that instantiate Prelude classes such as `Num`, one can+define instances of the new classes using the default implementation, eg:++```Haskell+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+import MixedTypesPrelude+import qualified Prelude as P++newtype II = II Integer deriving (P.Eq, P.Ord, P.Num) +instance CanAddAsymmetric II II+```++Conversely, if one defines instances such as `CanAddAsymmetric T T`,+one can then trivially define also instances `Num T` etc:++```Haskell+instance P.Num T where+  (+) = (+)+  ...+```+++## Testable specifications++The arithmetic type classes are accompanied by generic hspec test suites,+which are specialised to concrete instance types for their testing.+These test suites include the expected algebraic properties of operations,+such as commutativity and associativity of addition.++## Limitations++* Not all numerical operations are supported yet.+  Eg `tan`, `atan` are missing at the moment.++* Not all Prelude numerical types are supported yet.+  Eg `Natural` and `Float` are not supported at present,+  but `Double` is supported.++* Many common operations such as `fromEnum`, `threadDelay` give or require+  an `Int` value, which means we sometimes need to convert:++      threadDelay (int 1000000)+      integer (fromEnum True)++  Prelude functions such as `take`, `!!` and `length` that use `Int` in Prelude+  are shadowed in MixedTypesNumPrelude with more compatible/flexible versions.+  Beware that `Data.List.length` clashes with `length` in MixedTypesNumPrelude.++* Inferred types can be very large. Eg for `f a b c = sqrt (a + b * c + 1)` the inferred type is:++```Haskell+f :: (CanSqrt (AddType (AddType t2 (MulType t3 t4)) Integer),+      CanAddAsymmetric (AddType t2 (MulType t3 t4)) Integer,+      CanAddAsymmetric t2 (MulType t3 t4), CanMulAsymmetric t3 t4) =>+     t2+     -> t3+     -> t4+     -> SqrtType (AddType (AddType t2 (MulType t3 t4)) Integer)+```++<!-- * Due to limitations of some versions of ghc, type inference sometimes fails.+  Eg `add1 = (+ 1)` fails (eg with ghc 8.0.2) unless we explicitly declare the type+  @add1 :: (CanAdd Integer t) => t -> AddType t Integer@+  or use an explicit parameter, eg @add1 x = x + 1@. -->++## Credits++The idea of having numeric expressions in Haskell with types+derived bottom-up was initially suggested and implemented by Pieter Collins.+This version is a fresh rewrite by Michal Konečný.
mixed-types-num.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 49afc3a2c4bc0e8c25dc926023d6d0ae68fbdfc1880e1e70d30403256ddb1f11+-- hash: c571e2a889134066ae334e92527480bdc09db3eb49d6461c334c9184aa34be43  name:           mixed-types-num-version:        0.5.0.0+version:        0.5.0.1 synopsis:       Alternative Prelude with numeric and logic expressions typed bottom-up description:    Please see the README on GitHub at <https://github.com/michalkonecny/mixed-types-num#readme> category:       Math@@ -61,7 +61,7 @@   build-depends:       QuickCheck >=2.7     , base >=4.7 && <5-    , collect-errors ==0.1.*+    , collect-errors >=0.1.1     , hspec >=2.1     , hspec-smallcheck >=0.3     , mtl@@ -90,7 +90,7 @@   build-depends:       QuickCheck >=2.7     , base >=4.7 && <5-    , collect-errors ==0.1.*+    , collect-errors >=0.1.1     , hspec >=2.1     , hspec-smallcheck >=0.3     , mixed-types-num
src/MixedTypesNumPrelude.hs view
@@ -19,25 +19,10 @@  module MixedTypesNumPrelude (-  -- * Feature highlights--  -- ** Basics-  -- $basics--  -- ** Type classes-  -- $classes--  -- ** Testable specifications-  -- $specs--  -- * Limitations-  -- $limitations--  -- * Origin-  -- $origin-   -- * Re-exporting Prelude, hiding the operators we are changing   module Numeric.MixedTypes.PreludeHiding,+  -- * Error-collecting wrapper type+  CN, cn, unCN, (~!),   -- * A part of package ``convertible''   module Data.Convertible.Base,   -- * Modules with Prelude alternatives@@ -65,6 +50,7 @@ where  import Data.Ratio ((%))+import Numeric.CollectErrors (CN, cn, unCN, (~!)) import Data.Convertible.Instances.Num() import Data.Convertible.Base import Utils.TH.DeclForTypes@@ -86,222 +72,3 @@ import Numeric.MixedTypes.Elementary import Numeric.MixedTypes.Complex -{- $basics--To replicate the below in ghci using stack, start it as follows:-->> stack ghci mixed-types-num:lib->...> :add MixedTypesNumPrelude--=== Literals have a fixed type-->...> :t 1->... Integer-->...> :t 1.0->... Rational-->...> 1 :: Rational->... Couldn't match type ‘Integer’ with ‘GHC.Real.Ratio Integer’ ...--=== Mixed-type operations-->...> :t 1.5 + 1->... :: Rational-->...> :t 1.5 * (length [[]])->... :: Rational--=== Dividing integers, dealing with potential error-->...> :t let n = 1 in n/(n+1)->... :: Rational--To avoid runtime exceptions, it is recommended to use the CN error-collecting wrapper from package collect-errors:-->...> :t let n = cn 1 in n/(n+1)->... :: CN Rational--@CN@ is a synonym for @CollectErrors [(ErrorCertaintyLevel, NumError)] Rational@ as defined in module "Numeric.CollectErrors".-The @CN@ wrapper indicates that integer division can fail for some values:-->...> let n = cn 1 in n/(n-1)->{[(division by 0,ERROR)]}--Note that the error printed above is not an exception, but a special value.--All arithmetic operations have been extended to CN types so that it is possible to-have expressions that operate exclusively on CN types:-->...> f (n :: CN Integer) = 1/(1/(n-1) + 1/n) :: CN Rational->...> f (cn 0)->{[(division by 0,POTENTIAL ERROR),(division by 0,ERROR)]}->...> f (cn 1)->{[(division by 0,POTENTIAL ERROR),(division by 0,ERROR)]}->...> f (cn 2)->2 % 3--The function @hasError@ can be used to check whether any error occurred:-->...> hasError (cn 1/0)->True-->...> hasError (cn 1/1)->False--To extract a value from the CN wrapper, one can use function @withErrorOrValue@:-->...> withErrorOrValue (const 0.0) id (cn 1/2)->1 % 2--The following examples require also package <https://github.com/michalkonecny/aern2 aern2-real>.-To get access to this via stack, you can start ghci eg as follows:--> stack ghci aern2-real:lib->...> :add AERN2.Real--Also other harmless potential errors can be ignored using @(~!)@:-->...> (~!) $ sqrt (pi-pi) ? (bitsS 10)-> [0.000007629... ± 7.6294e-6 <2^(-17)]-->...> sqrt (pi-pi) ? (bitsS 10)-> [0.000007629... ± 7.6294e-6 <2^(-17)]{[(POTENTIAL ERROR,out of range: sqrt: argument must be >= 0: [0 ± 2.3283e-10 <2^(-32)])]}--When an error is present (which can be checked using hasErrorCN), the function hasCertainErrorCN can be used to further distinguish cases where the error is certain or potential:-->...> hasCertainErrorCN (sqrt (-1) ? (bitsS 10))->True-->...> hasCertainErrorCN (sqrt (pi-pi) ? (bitsS 10))->False---=== Natural, integer and fractional powers-->...> :t 2^2->...CN Integer-->...> :t 2.0^(-2)->...CN Rational-->...> :t (double 2)^(1/!2)->...Double--The following examples require package <https://github.com/michalkonecny/aern2 aern2-real>:-->...> :t 2^(1/2)->...CauchyRealCN-->...> :t pi->...CauchyReal-->...> :t sqrt 2->...CauchyRealCN--=== Comparing an integer with an (exact) real number-->...> let abs2 x = if x < 0 then -x else x in (abs2 (pi - pi)) ? (bitsS 100)->[0 ± <2^(-103)]{[(POTENTIAL ERROR,numeric error: union of enclosures: not enclosing the same value)]}--The potential error means that both branches were executed in parallel because-the condition could not be decided, and it was moreover impossible to guarantee-(in general) that both branches will return the same number.  If we make a mistake,-this error may appear with certainty, eg:-->...> let abs2 x = if x < 0 then 1-x else x in (abs2 (pi - pi)) ? (bitsS 100)->*** Exception: WithGlobalParam ensureNoCE: [(ERROR,numeric error: union of enclosures: not enclosing the same value)]--If we are certain such errors will never appear, we can silence the potential error warnings:-->...> let abs2 x = (~!) (if x < 0 then -x else x) in (abs2 (pi - pi)) ? (bitsS 100)->[0 ± <2^(-103)]--In these examples, @if@ is overloaded so that it works for conditions-of other types than @Bool@.  Here the condition has the type @Sequence (Maybe Bool)@.-The whole expression is the sequence of balls in which those balls for which the condition-is inconclusive are the union of the balls computed by both branches.---}--{- $classes-    Arithmetic operations are provided via multi-parameter type classes-    and the result type is given by associated-    type families. For example:--    > (+) :: (CanAddAsymmetric t1 t2) => t1 -> t2 -> AddType t1 t2--    The type constraint @CanAdd t1 t2@ implies both-    @CanAddAsymmetric t1 t2@ and @CanAddAsymmetric t2 t1@.--    For convenience there are other aggregate type constraints such as-    @CanAddThis t1 t2@, which implies that the result is of type @t1@,-    and @CanAddSameType t@, which is a shortcut for @CanAddThis t t@.--    Notably, there are convenience classes `Ring` and `Field`.--    For types that instantiate Prelude classes such as `Num`, one can-    define instances of the new classes using the default implementation, eg:---    > newtype II = II Integer deriving (Eq, Ord, Num) -- assuming -XGeneralizedNewtypeDeriving-    > instance CanAddAsymmetric II II--    Conversely, if one defines instances for classes such as `CanAddAsymmetric`,-    one can then trivially define also instances of `Num` etc:--    > instance Prelude.Num T where-    >   (+) = (+)-    >   ...---}--{- $specs-    The arithmetic type classes are accompanied by generic hspec test suites,-    which are specialised to concrete instance types for their testing.-    These test suites include the expected algebraic properties of operations,-    such as commutativity and associativity of addition.--}--{- $limitations-    * Not all numerical operations are supported yet.-      Eg @tan@, @atan@ are missing at the moment.--    * Not all Prelude numerical types are supported yet.-      Eg @Natural@ and @Float@ are not supported at present,-      but @Double@ is supported.--    * Many common operations such as @fromEnum@, @threadDelay@ give or require-      an @Int@ value, which means we sometimes need to convert:--      > threadDelay (int 1000000)-      > integer (fromEnum True)--      Prelude functions such as @take@, @!!@ and @length@ that use @Int@ in Prelude-      are shadowed in @MixedTypesNumPrelude@ with more compatible/flexible versions.-      Beware that @Data.List.length@ clashes with @length@ in @MixedTypesNumPrelude@.--    * Inferred types can be very large. Eg for @f a b c = sqrt (a + b * c + 1)@ the inferred type is:--    >  f: (CanMulAsymmetric t1 t2, CanAddAsymmetric t4 (MulType t1 t2),-    >      CanAddAsymmetric (AddType t4 (MulType t1 t2)) Integer,-    >      CanSqrt (AddType (AddType t4 (MulType t1 t2)) Integer)) =>-    >     t4-    >     -> t1-    >     -> t2-    >     -> SqrtType (AddType (AddType t4 (MulType t1 t2)) Integer)--    * Due to limitations of some versions of ghc, type inferrence sometimes fails.-      Eg @add1 = (+ 1)@ fails (eg with ghc 8.0.2) unless we explicitly declare the type-      @add1 :: (CanAdd Integer t) => t -> AddType t Integer@-      or use an explicit parameter, eg @add1 x = x + 1@.---}--{- $origin--    The idea of having numeric expressions in Haskell with types-    derived bottom-up was initially suggested and implemented by Pieter Collins.-    This version is a fresh rewrite by Michal Konečný.---}
src/Numeric/MixedTypes/Div.hs view
@@ -63,9 +63,9 @@   (t1 -> t2 -> t3) ->   CN t1 -> CN t2 -> CN t3 divideCN unsafeDivide a b-  | isCertainlyZero b = CN.noValueNumErrorCertain e+  | isCertainlyZero b = CN.removeValueErrorCertain r e   | isCertainlyNonZero b = r-  | otherwise = CN.noValueNumErrorPotential e+  | otherwise = CN.removeValueErrorPotential r e   where   r = CN.lift2 unsafeDivide a b   e :: CN.NumError
src/Numeric/MixedTypes/Elementary.hs view
@@ -104,9 +104,10 @@   type SqrtType (CN a) = CN (SqrtType a)   sqrt x      | isCertainlyNonNegative x = CN.lift sqrt x-    | isCertainlyNegative x = CN.noValueNumErrorCertain err-    | otherwise = CN.prependErrorPotential err $ CN.lift sqrt $ max x 0+    | isCertainlyNegative x = CN.removeValueErrorCertain sqrtx err+    | otherwise = CN.prependErrorPotential err sqrtx     where+    sqrtx = CN.lift sqrt $ max x 0     err :: CN.NumError     err = CN.OutOfDomain "negative sqrt argument" @@ -228,8 +229,8 @@   type LogType (CN a) = CN (LogType a)   log x      | isCertainlyPositive x = logx-    | isCertainlyNonPositive x = CN.noValueNumErrorCertain err-    | otherwise = CN.noValueNumErrorPotential err+    | isCertainlyNonPositive x = CN.removeValueErrorCertain logx err+    | otherwise = CN.removeValueErrorPotential logx err     where     logx = CN.lift log x     err :: CN.NumError
src/Numeric/MixedTypes/Round.hs view
@@ -38,7 +38,7 @@ import Test.Hspec import Test.QuickCheck as QC -import Numeric.CollectErrors ( CN )+import Numeric.CollectErrors ( CN, cn, unCN ) import qualified Numeric.CollectErrors as CN  import Numeric.MixedTypes.Literals@@ -76,13 +76,13 @@   type ModType (CN t1) (CN t2) = (CN (ModType t1 t2))   divIMod x m     | isCertainlyPositive m = (d, xm)-    | isCertainlyNegative m = (noval, noval)+    | isCertainlyNegative m = (noval d, noval xm)     | otherwise = (errPote d, errPote xm)     where     (d,xm) = CN.lift2pair divIMod x m -noval :: CN v-noval = CN.noValueNumErrorCertain err+noval :: CN v -> CN v+noval = flip CN.removeValueErrorCertain err errPote :: CN t -> CN t errPote = CN.prependErrorPotential err err :: CN.NumError@@ -97,7 +97,7 @@       type ModType (CN t1) $t = (CN (ModType t1 $t))       divIMod x m         | isCertainlyPositive m = (d, xm)-        | isCertainlyNegative m = (noval, noval)+        | isCertainlyNegative m = (noval d, noval xm)         | otherwise = (errPote d, errPote xm)         where         (d,xm) = CN.lift1Tpair divIMod x m@@ -107,7 +107,7 @@       type ModType $t (CN t2) = (CN (ModType $t t2))       divIMod x m         | isCertainlyPositive m = (d, xm)-        | isCertainlyNegative m = (noval, noval)+        | isCertainlyNegative m = (noval d, noval xm)         | otherwise = (errPote d, errPote xm)         where         (d,xm) = CN.liftT1pair divIMod x m