packages feed

collect-errors-0.1.4.0: src/Numeric/CollectErrors/PreludeInstances.hs

{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Numeric.CollectErrors.PreludeInstances where

import Prelude

import Control.CollectErrors ( CollectErrors(CollectErrors), lift, lift2 )
import Control.CollectErrors.PreludeInstances ( liftGotValue )
import Numeric.CollectErrors.Type

instance (Fractional v, Eq v) => Fractional (CN v) where
  fromRational = pure . fromRational
  recip = liftAcheck (==0) (\_ -> DivByZero) recip
  (/) = liftA2checkB (==0) (\_ -> DivByZero) (/)

instance (Integral v, Ord v, Show v) => Integral (CN v) where
  quotRem (CollectErrors (Just a) ae) (CollectErrors (Just b) be) 
    | b <= 0 = (e,e)
    | otherwise = (CollectErrors (Just q) es, CollectErrors (Just r) es)
    where
    (q,r) = quotRem a b
    es = ae <> be
    e = noValueNumErrorCertain (OutOfDomain $ "quotRem with non-positive denominator " ++ show b)
  quotRem (CollectErrors _ ae) (CollectErrors _ be) = (e,e)
    where
    e = CollectErrors Nothing (ae <> be)
  toInteger = liftGotValue "toInteger" toInteger

instance (Floating v, Ord v, Show v) => Floating (CN v) where
  pi = pure pi
  exp = lift exp
  log = liftAcheckPositive "log" log
  sqrt = liftAcheckNonnegative "sqrt" sqrt
  (**) = lift2 (**) -- TODO: domain check
  logBase = lift2 logBase -- TODO: domain check
  sin = lift sin
  cos = lift cos
  asin = liftAcheckPlusMinusOne "asin" asin
  acos = liftAcheckPlusMinusOne "acos" acos
  atan = lift atan
  sinh = lift sinh
  cosh = lift cosh
  asinh = lift asinh
  acosh = lift acosh
  atanh = lift atanh

liftAcheck :: 
  (a -> Bool) -> 
  (a -> NumError) -> 
  (a -> v) -> CN a -> CN v
liftAcheck check err _op (CollectErrors (Just a) _)
  | check a = noValueNumErrorCertain (err a)
liftAcheck _ _ op aCN = lift op aCN

liftAcheckPositive :: (Ord a, Num a, Show a) => String -> (a -> v) -> CN a -> CN v
liftAcheckPositive fnName =
  liftAcheck (<=0) (\x -> OutOfDomain $ fnName ++ " for non-positive arg " ++ show x)

liftAcheckNonnegative :: (Ord a, Num a, Show a) => String -> (a -> v) -> CN a -> CN v
liftAcheckNonnegative fnName =
  liftAcheck (<0) (\x -> OutOfDomain $ fnName ++ " for negative arg " ++ show x)

liftAcheckPlusMinusOne :: (Ord a, Num a, Show a) => String -> (a -> v) -> CN a -> CN v
liftAcheckPlusMinusOne fnName =
  liftAcheck (\x -> -1 <= x && x <= 1) (\x -> OutOfDomain $ fnName ++ " for illegal arg " ++ show x)

liftA2checkB :: 
  (b -> Bool) -> 
  (b -> NumError) -> 
  (a -> b -> v) -> 
  CN a -> CN b -> CN v
liftA2checkB checkB errB _op _a (CollectErrors (Just b) _)
  | checkB b = noValueNumErrorCertain (errB b)
liftA2checkB _ _ op a bCN = lift2 op a bCN