packages feed

numhask-0.12.0.0: src/NumHask/Data/Positive.hs

{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

-- | Field classes
module NumHask.Data.Positive
  ( Positive (..),
    positive,
    maybePositive,
    positive_,
    Monus (..),
    Addus (..),
    MonusSemiField,
  )
where

import Control.Category ((>>>))
import Data.Bool (bool)
import Data.Maybe
import NumHask.Algebra.Action
import NumHask.Algebra.Additive
import NumHask.Algebra.Field
import NumHask.Algebra.Lattice
import NumHask.Algebra.Metric
import NumHask.Algebra.Multiplicative
import NumHask.Algebra.Ring
import NumHask.Data.Integral
import NumHask.Data.Rational
import NumHask.Data.Wrapped
import Prelude (Eq, Ord, Show)
import Prelude qualified as P

-- $setup
--
-- >>> :set -XRebindableSyntax
-- >>> import NumHask.Prelude
-- >>> import NumHask.Data.Positive

-- | zero is positive
--
-- >>> 1 :: Positive Int
-- UnsafePositive {unPositive = 1}
--
-- >>> positive 0 == zero
-- True
--
-- >>> positive (-1)
-- UnsafePositive {unPositive = 0}
--
-- >>> maybePositive (-1)
-- Nothing
newtype Positive a = UnsafePositive {unPositive :: a}
  deriving stock
    (Eq, Ord, Show)
  deriving
    ( Additive,
      Multiplicative,
      Divisive,
      Integral,
      FromInteger,
      FromRational,
      Basis,
      Direction,
      Epsilon,
      AdditiveAction,
      SubtractiveAction,
      MultiplicativeAction,
      DivisiveAction,
      JoinSemiLattice,
      MeetSemiLattice,
      BoundedMeetSemiLattice
    )
    via (Wrapped a)

instance (MeetSemiLattice a, Integral a) => FromIntegral (Positive a) a where
  fromIntegral a = positive a

instance (FromIntegral a b) => FromIntegral (Positive a) b where
  fromIntegral a = UnsafePositive (fromIntegral a)

instance (ToIntegral a b) => ToIntegral (Positive a) b where
  toIntegral (UnsafePositive a) = toIntegral a

instance (FromRatio a b) => FromRatio (Positive a) b where
  fromRatio a = UnsafePositive (fromRatio a)

instance (ToRatio a b) => ToRatio (Positive a) b where
  toRatio (UnsafePositive a) = toRatio a

instance (Additive a, JoinSemiLattice a) => BoundedJoinSemiLattice (Positive a) where
  bottom = UnsafePositive zero

instance QuotientField (Positive P.Double) where
  type Whole (Positive P.Double) = Positive P.Int
  properFraction (UnsafePositive a) = (\(n, r) -> (UnsafePositive n, UnsafePositive r)) (P.properFraction a)
  ceiling = properFraction >>> P.fst >>> (+ one)
  floor = properFraction >>> P.fst
  truncate = floor
  round x = case properFraction x of
    (n, r) ->
      let half_up = r + half
       in case P.compare half_up one of
            P.LT -> n
            P.EQ -> bool (n + one) n (even n)
            P.GT -> n + one

-- | Constructor which returns zero for a negative input.
--
-- >>> positive (-1)
-- UnsafePositive {unPositive = 0}
positive :: (Additive a, MeetSemiLattice a) => a -> Positive a
positive a = UnsafePositive (a /\ zero)

-- | Unsafe constructors.
--
-- >>> positive_ (-one)
-- UnsafePositive {unPositive = -1}
positive_ :: a -> Positive a
positive_ = UnsafePositive

-- | Constructor which returns Nothing for a negative number.
-- >>> maybePositive (-one)
-- Nothing
maybePositive :: (Additive a, MeetSemiLattice a) => a -> Maybe (Positive a)
maybePositive a = bool Nothing (Just (UnsafePositive a)) (a `meetLeq` zero)

instance (Subtractive a, MeetSemiLattice a) => Monus (Positive a) where
  (UnsafePositive a) ∸ (UnsafePositive b) = positive (a - b)

-- | A field but with truncated subtraction.
type MonusSemiField a = (Monus a, Distributive a, Divisive a)

-- | <https://en.wikipedia.org/wiki/Monus Monus> or truncated subtraction.
--
-- >>> positive 4 ∸ positive 7
-- UnsafePositive {unPositive = 0}
--
-- >>> 4 ∸ 7 :: Positive Int
-- UnsafePositive {unPositive = 0}
class Monus a where
  {-# MINIMAL (∸) #-}

  infixl 6 ∸
  (∸) :: a -> a -> a
  default (∸) :: (BoundedJoinSemiLattice a, MeetSemiLattice a, Subtractive a) => a -> a -> a
  a ∸ b = bottom /\ (a - b)

-- | Truncated addition
class Addus a where
  {-# MINIMAL (∔) #-}
  infixl 6 ∔
  (∔) :: a -> a -> a
  default (∔) :: (BoundedMeetSemiLattice a, JoinSemiLattice a, Additive a) => a -> a -> a
  a ∔ b = top \/ (a + b)