collect-errors
Error monad with a Float instance
https://github.com/michalkonecny/collect-errors#readme
LTS Haskell 23.2: | 0.1.6.0 |
Stackage Nightly 2024-12-30: | 0.1.6.0 |
Latest on Hackage: | 0.1.6.0 |
collect-errors-0.1.6.0@sha256:a146517caf3b550f4c77090b63da2ad915dc7051ac8cff3b546cf384eb811894,1297
Module documentation for 0.1.6.0
collect-errors
This package provides an error collecting mechanism. Using CN Double
instead of Double
replaces NaNs and infinities with more informative error descriptions.
API documentation available on the Hackage page.
Table of contents
1. Feature highlights
CollectErrors es t
is a monad wrapper around values of type t
which can accommodate (a list of)
(potential) errors of type es
that have (maybe) occurred during the computation
of a value. A value may be missing, leaving only the error(s).
The wrapper CN t
is a special case of CollectErrors es t
with es
= NumErrors
.
1.1. Error collecting
The CN
wrapper propagates instances of Floating
,
allowing us to write expressions with partial
functions (ie functions that fail for some inputs) instead of
branching after each application of such function:
$ stack ghci collect-errors:lib --no-load --ghci-options Numeric.CollectErrors
*Numeric.CollectErrors> a = 1 :: CN Double
*Numeric.CollectErrors> (1/(a-1))+(sqrt (a-2))
{{ERROR: division by 0; ERROR: out of domain: sqrt for negative arg -1.0}}
as opposed to:
*Prelude> a = 1 :: Double
*Prelude> (1/(a-1))+(sqrt (a-2))
NaN
1.2. Error investigation
Dealing with the errors can be moved outside the expression:
*Numeric.CollectErrors> a = 1 :: CN Double
*Numeric.CollectErrors> toEither $ 1/(a-1)
Left {ERROR: division by 0}
*Numeric.CollectErrors> toEither $ 1/a+(sqrt a)
Right 2.0
An alternative way to branch based on errors is provided by the function withErrorOrValue
:
...> a = 2 :: CN Double
...> withErrorOrValue (const 0) id (1/a)
0.5
The CN
wrapper can be forcibly removed as follows:
...> :t unCN (1/a)
... :: Double
...> unCN (1/a)
0.5
...> unCN (1/(a-2))
*** Exception: CollectErrors: {ERROR: division by 0}
1.3. Undecided comparisons
The following examples require the interval arithmetic package aern2-mp and its dependency mixed-types-num:
$ stack ghci aern2-mp:lib --no-load --ghci-options AERN2.MP
*AERN2.MP> import MixedTypesNumPrelude
*AERN2.MP MixedTypesNumPrelude>
Comparisons involving sets (such as intervals) are undecided when the intervals overlap:
...> pi100 = piBallP (prec 100)
...> :t pi100
pi100 :: MPBall
...> pi100
[3.1415926535897932384626433832793333156439620213... ± ~7.8886e-31 ~2^(-100)]
...> 0 < pi100
CertainTrue
...> pi100 == pi100
TrueOrFalse
The values CertainTrue
and TrueOrFalse
are part of the three-valued type Kleenean
provided by
mixed-types-num.
The above equality cannot be decided since pi100
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.
The Prelude Floating
instance for CN
cannot be used reliably with interval arithmetic because the instance relies on true/false comparisons:
...> import qualified Prelude as P
... P> (cn pi100) P./ (cn pi100 - cn pi100)
*** Exception: Failed to decide equality of MPBalls. If you switch to MixedTypesNumPrelude instead of Prelude, comparison of MPBalls returns Kleenean instead of Bool.
Using its Kleenean comparisons, package mixed-types-num provides alternative numerical type classes in which errors are detected and collected correctly when using the CN
wrapper:
...> (cn pi100) / (cn pi100 - cn pi100)
{{POTENTIAL ERROR: division by 0}}
1.4. Potential errors
As we see in the above example, the CN
wrapper supports potential errors that sometimes arise as a consequence of undecided comparisons.
When an error is present (which can be checked using hasError
), the function hasCertainError
can be used to further distinguish cases where the error is certain or potential:
...> import qualified Numeric.CollectErrors as CN
...> CN.hasCertainError $ (cn pi100) / (cn 0)
True
...> CN.hasCertainError $ (cn pi100) / (cn pi100 - cn pi100)
False
Sometimes the potential errors are harmless:
...> sqrt (cn pi100 - cn pi100)
[0.0000000000000006280369834735100420368561502297... ± ~6.2804e-16 ~2^(-50)]{{POTENTIAL ERROR: out of domain: negative sqrt argument}}
Such harmless potential errors can be ignored using clearPotentialErrors
:
...> clearPotentialErrors $ sqrt (cn pi100 - cn pi100)
[0.0000000000000006280369834735100420368561502297... ± ~6.2804e-16 ~2^(-50)]
Changes
Changelog for collect-errors
v0.1.6
- Add utilities unCNfn1, unCNfn2
v0.1.5
- Make clearPotentialErrors generic, with instances for CN, pairs and lists
v0.1.4
- Add clearPotentialErrors for removing harmless errors
- Improve documentation
v0.1.3
- Add CanTakeCNErrors type shortcut
v0.1.2
- Add CanTakeErrors type class and liftTakeErrors function
- Add instance of deepseq’s NFData
v0.1.1
- Add removeValue functions
v0.1.0
- Initial port of CollectErrors and CN from mixed-types-num-0.4.1
- Simplify the code by abandoning EnsureCE and related type functions and utilities
- NumErrors within CN are now a set to prevent multiple occurrences of the same error
- Add CollectError or CN instances for various Prelude type classes, including Num and Floating, checking for domain violations