natural-0.5.0.1: src/Natural.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Natural
( Natural,
HasNatural (..),
AsNatural (..),
ProductNatural (..),
MaxNatural (..),
MinNatural (..),
zero,
zero',
successor,
successor',
plus,
multiply,
power,
zeroOr,
length,
replicate,
take,
drop,
splitAt,
(!!),
findIndices,
findIndex,
elemIndices,
elemIndex,
minus,
list,
Positive,
HasPositive (..),
AsPositive (..),
SumPositive (..),
MaxPositive (..),
MinPositive (..),
naturalPositive,
one,
one',
successor1,
successor1',
successorW,
plus1,
multiply1,
power1,
oneOr,
length1,
replicate1,
take1,
drop1,
splitAt1,
(!!!),
findIndices1,
findIndex1,
elemIndices1,
elemIndex1,
minus1,
list1,
plusone,
minusone,
NotZero (..),
HasNotZero (..),
AsNotZero (..),
SumNotZero (..),
MaxNotZero (..),
MinNotZero (..),
positiveNotZero,
negativeNotZero,
notZeroPositive,
notZeroInteger,
isPositive,
isNegative,
negateNZ,
absoluteNZ,
signumNZ,
plusNZ,
multiplyNZ,
notZeroOr,
toJsonNatural,
parseJsonNatural,
toJsonPositive,
parseJsonPositive,
toJsonNotZero,
parseJsonNotZero,
)
where
import Control.Applicative (Const, pure)
import Control.Category (id, (.))
import Control.Lens (Iso', Lens', Prism', Rewrapped, Wrapped (Unwrapped, _Wrapped'), iso, prism', (#), (^.), (^?), _Wrapped)
import Control.Monad (fail, (>>=))
import Data.Aeson.Types
( FromJSON (parseJSON),
Parser,
ToJSON (toEncoding, toJSON),
Value,
)
import Data.Bool (Bool (False, True), not, (&&))
import Data.Eq (Eq ((==)))
import Data.Foldable (Foldable (foldl'))
import Data.Function (const)
import Data.Functor.Identity (Identity)
import Data.Int (Int)
import Data.List (filter, iterate, map, repeat, zip)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NonEmpty (filter, iterate, zip)
import Data.Maybe (Maybe (Just, Nothing), fromMaybe, listToMaybe)
import Data.Monoid (Monoid (mappend, mempty))
import Data.Ord (Ord (compare, (<), (<=)), max, min)
import Data.Semigroup (Semigroup ((<>)))
import Data.Semigroup.Foldable (Foldable1 (foldMap1))
import Data.Tuple (fst, snd)
import Data.Word (Word)
import Prelude (Integer, Integral, Show, abs, fromIntegral, negate, (*), (+), (-), (^))
-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Control.Lens((^?), (^.), (#), _Wrapped', _Wrapped)
-- >>> import Data.Aeson(encode, decode, fromJSON, Result(..))
-- >>> import Data.Aeson.Types(parse, Value(Number))
-- >>> import Data.List.NonEmpty(NonEmpty(..))
-- >>> import Data.Maybe(fromJust)
-- >>> let nat n = fromJust ((n :: Integer) ^? _Natural)
-- >>> let pos n = fromJust ((n :: Integer) ^? _Positive)
-- | A natural number (>= 0) represented as a newtype over 'Integer'.
--
-- >>> nat 0
-- Natural 0
--
-- >>> nat 5
-- Natural 5
newtype Natural
= Natural
Integer
deriving (Eq, Ord, Show)
-- |
--
-- >>> nat 3 <> nat 4
-- Natural 7
--
-- >>> nat 0 <> nat 5
-- Natural 5
instance Semigroup Natural where
Natural x <> Natural y =
Natural (x + y)
-- |
--
-- >>> mempty :: Natural
-- Natural 0
instance Monoid Natural where
mappend =
(<>)
mempty =
Natural 0
class HasNatural a where
natural ::
Lens'
a
Natural
-- |
--
-- >>> (nat 5) ^. natural
-- Natural 5
instance HasNatural Natural where
natural =
id
class AsNatural a where
_Natural ::
Prism'
a
Natural
-- |
--
-- >>> _Natural # nat 5 :: Natural
-- Natural 5
instance AsNatural Natural where
_Natural =
id
integralPrism ::
(Integral a) =>
Prism'
a
Natural
integralPrism =
prism'
(\(Natural n) -> fromIntegral n)
(\n -> if n < 0 then Nothing else Just (Natural (fromIntegral n)))
-- |
--
-- >>> (5 :: Int) ^? _Natural
-- Just (Natural 5)
--
-- >>> (-1 :: Int) ^? _Natural
-- Nothing
instance AsNatural Int where
_Natural =
integralPrism
-- |
--
-- >>> (42 :: Integer) ^? _Natural
-- Just (Natural 42)
--
-- >>> (-1 :: Integer) ^? _Natural
-- Nothing
instance AsNatural Integer where
_Natural =
integralPrism
-- |
--
-- >>> (7 :: Word) ^? _Natural
-- Just (Natural 7)
instance AsNatural Word where
_Natural =
integralPrism
-- |
--
-- >>> import Data.Functor.Identity(Identity(..))
-- >>> import Control.Applicative(Const(..))
-- >>> (Const 5 :: Const Integer Bool) ^? _Natural
-- Just (Natural 5)
instance (Integral a) => AsNatural (Const a b) where
_Natural =
integralPrism
-- |
--
-- >>> import Data.Functor.Identity(Identity(..))
-- >>> (Identity 5 :: Identity Integer) ^? _Natural
-- Just (Natural 5)
instance (Integral a) => AsNatural (Identity a) where
_Natural =
integralPrism
-- |
--
-- >>> ProductNatural (nat 3) <> ProductNatural (nat 4)
-- ProductNatural (Natural 12)
--
-- >>> mempty :: ProductNatural
-- ProductNatural (Natural 1)
newtype ProductNatural
= ProductNatural
Natural
deriving (Eq, Ord, Show)
-- |
--
-- >>> ProductNatural (nat 5) ^. natural
-- Natural 5
instance HasNatural ProductNatural where
natural =
_Wrapped . natural
-- |
--
-- >>> ProductNatural (nat 5) ^? _Natural
-- Just (Natural 5)
instance AsNatural ProductNatural where
_Natural =
_Wrapped . _Natural
instance
(ProductNatural ~ a) =>
Rewrapped ProductNatural a
-- |
--
-- >>> ProductNatural (nat 5) ^. _Wrapped'
-- Natural 5
instance Wrapped ProductNatural where
type Unwrapped ProductNatural = Natural
_Wrapped' =
iso
(\(ProductNatural x) -> x)
ProductNatural
-- |
--
-- >>> ProductNatural (nat 3) <> ProductNatural (nat 4)
-- ProductNatural (Natural 12)
instance Semigroup ProductNatural where
ProductNatural (Natural x) <> ProductNatural (Natural y) =
ProductNatural (Natural (x * y))
-- |
--
-- >>> mempty :: ProductNatural
-- ProductNatural (Natural 1)
instance Monoid ProductNatural where
mappend =
(<>)
mempty =
ProductNatural (Natural 1)
-- |
--
-- >>> MaxNatural (nat 3) <> MaxNatural (nat 7)
-- MaxNatural (Natural 7)
newtype MaxNatural
= MaxNatural
Natural
deriving (Eq, Ord, Show)
-- |
--
-- >>> MaxNatural (nat 7) ^. natural
-- Natural 7
instance HasNatural MaxNatural where
natural =
_Wrapped . natural
-- |
--
-- >>> MaxNatural (nat 7) ^? _Natural
-- Just (Natural 7)
instance AsNatural MaxNatural where
_Natural =
_Wrapped . _Natural
instance
(MaxNatural ~ a) =>
Rewrapped MaxNatural a
-- |
--
-- >>> MaxNatural (nat 7) ^. _Wrapped'
-- Natural 7
instance Wrapped MaxNatural where
type Unwrapped MaxNatural = Natural
_Wrapped' =
iso
(\(MaxNatural x) -> x)
MaxNatural
-- |
--
-- >>> MaxNatural (nat 3) <> MaxNatural (nat 7)
-- MaxNatural (Natural 7)
instance Semigroup MaxNatural where
MaxNatural (Natural x) <> MaxNatural (Natural y) =
MaxNatural (Natural (x `max` y))
-- |
--
-- >>> MinNatural (nat 3) <> MinNatural (nat 7)
-- MinNatural (Natural 3)
newtype MinNatural
= MinNatural
Natural
deriving (Eq, Ord, Show)
-- |
--
-- >>> MinNatural (nat 3) ^. natural
-- Natural 3
instance HasNatural MinNatural where
natural =
_Wrapped . natural
-- |
--
-- >>> MinNatural (nat 3) ^? _Natural
-- Just (Natural 3)
instance AsNatural MinNatural where
_Natural =
_Wrapped . _Natural
instance
(MinNatural ~ a) =>
Rewrapped MinNatural a
-- |
--
-- >>> MinNatural (nat 3) ^. _Wrapped'
-- Natural 3
instance Wrapped MinNatural where
type Unwrapped MinNatural = Natural
_Wrapped' =
iso
(\(MinNatural x) -> x)
MinNatural
-- |
--
-- >>> MinNatural (nat 3) <> MinNatural (nat 7)
-- MinNatural (Natural 3)
instance Semigroup MinNatural where
MinNatural (Natural x) <> MinNatural (Natural y) =
MinNatural (Natural (x `min` y))
-- | Serialises a 'Natural' to a JSON number.
--
-- >>> fromJSON (Number 0) :: Result Natural
-- Success (Natural 0)
--
-- >>> fromJSON (Number 42) :: Result Natural
-- Success (Natural 42)
--
-- >>> decode "42" :: Maybe Natural
-- Just (Natural 42)
--
-- >>> decode "0" :: Maybe Natural
-- Just (Natural 0)
--
-- >>> decode "-1" :: Maybe Natural
-- Nothing
instance ToJSON Natural where
toJSON =
toJsonNatural
toEncoding (Natural n) =
toEncoding n
-- | Parses a 'Natural' from a JSON number, failing on negative values.
--
-- >>> decode "0" :: Maybe Natural
-- Just (Natural 0)
instance FromJSON Natural where
parseJSON =
parseJsonNatural
-- | Serialises any value with a 'HasNatural' instance to a JSON 'Value'.
--
-- >>> toJsonNatural (nat 42)
-- Number 42.0
--
-- >>> toJsonNatural (ProductNatural (nat 12))
-- Number 12.0
--
-- >>> toJsonNatural (MaxNatural (nat 7))
-- Number 7.0
--
-- >>> toJsonNatural (MinNatural (nat 3))
-- Number 3.0
{-# SPECIALIZE toJsonNatural ::
Natural ->
Value
#-}
{-# INLINE toJsonNatural #-}
toJsonNatural ::
(HasNatural a) =>
a ->
Value
toJsonNatural a =
let Natural n = a ^. natural
in toJSON n
-- | Parses a JSON value into a 'Natural', failing on negative values.
--
-- >>> parse parseJsonNatural (Number 42)
-- Success (Natural 42)
--
-- >>> parse parseJsonNatural (Number 0)
-- Success (Natural 0)
--
-- >>> parse parseJsonNatural (Number (-1))
-- Error "parse failed, Natural: expected non-negative integer"
{-# INLINE parseJsonNatural #-}
parseJsonNatural ::
Value ->
Parser Natural
parseJsonNatural v =
parseJSON v >>= \n ->
if n < 0
then fail "parse failed, Natural: expected non-negative integer"
else pure (Natural n)
-- | Serialises a 'ProductNatural' to a JSON number.
--
-- >>> encode (ProductNatural (nat 12))
-- "12"
instance ToJSON ProductNatural where
toJSON =
toJsonNatural
toEncoding (ProductNatural n) =
toEncoding n
-- | Parses a 'ProductNatural' from a JSON number, failing on negative values.
--
-- >>> decode "12" :: Maybe ProductNatural
-- Just (ProductNatural (Natural 12))
--
-- >>> decode "-1" :: Maybe ProductNatural
-- Nothing
instance FromJSON ProductNatural where
parseJSON v =
parseJsonNatural v >>= \n -> pure (ProductNatural n)
-- | Serialises a 'MaxNatural' to a JSON number.
--
-- >>> encode (MaxNatural (nat 7))
-- "7"
instance ToJSON MaxNatural where
toJSON =
toJsonNatural
toEncoding (MaxNatural n) =
toEncoding n
-- | Parses a 'MaxNatural' from a JSON number, failing on negative values.
--
-- >>> decode "7" :: Maybe MaxNatural
-- Just (MaxNatural (Natural 7))
--
-- >>> decode "-1" :: Maybe MaxNatural
-- Nothing
instance FromJSON MaxNatural where
parseJSON v =
parseJsonNatural v >>= \n -> pure (MaxNatural n)
-- | Serialises a 'MinNatural' to a JSON number.
--
-- >>> encode (MinNatural (nat 3))
-- "3"
instance ToJSON MinNatural where
toJSON =
toJsonNatural
toEncoding (MinNatural n) =
toEncoding n
-- | Parses a 'MinNatural' from a JSON number, failing on negative values.
--
-- >>> decode "3" :: Maybe MinNatural
-- Just (MinNatural (Natural 3))
--
-- >>> decode "-1" :: Maybe MinNatural
-- Nothing
instance FromJSON MinNatural where
parseJSON v =
parseJsonNatural v >>= \n -> pure (MinNatural n)
-- | Prism matching zero.
--
-- >>> zero # ()
-- Natural 0
--
-- >>> nat 0 ^? zero
-- Just ()
--
-- >>> nat 3 ^? zero
-- Nothing
zero ::
Prism'
Natural
()
zero =
prism'
(\() -> Natural 0)
(\(Natural n) -> if n == 0 then Just () else Nothing)
-- | The zero natural number.
--
-- >>> zero'
-- Natural 0
zero' ::
Natural
zero' =
zero # ()
-- | Prism between a natural and its predecessor.
--
-- >>> successor # nat 0
-- Natural 1
--
-- >>> successor # nat 4
-- Natural 5
--
-- >>> nat 5 ^? successor
-- Just (Natural 4)
--
-- >>> nat 0 ^? successor
-- Nothing
successor ::
Prism'
Natural
Natural
successor =
prism'
(\(Natural n) -> Natural (n + 1))
(\(Natural n) -> if n == 0 then Nothing else Just (Natural (n - 1)))
-- | The successor of a natural number.
--
-- >>> successor' (nat 0)
-- Natural 1
--
-- >>> successor' (nat 4)
-- Natural 5
successor' ::
Natural ->
Natural
successor' =
(successor #)
-- | Add two natural numbers.
--
-- >>> plus (nat 3) (nat 4)
-- Natural 7
--
-- >>> plus (nat 0) (nat 5)
-- Natural 5
plus ::
Natural ->
Natural ->
Natural
plus =
(<>)
-- | Multiply two natural numbers.
--
-- >>> multiply (nat 3) (nat 4)
-- Natural 12
--
-- >>> multiply (nat 0) (nat 5)
-- Natural 0
multiply ::
Natural ->
Natural ->
Natural
multiply x y =
(_Wrapped # x <> (_Wrapped # y :: ProductNatural)) ^. _Wrapped
-- | Raise a natural to a natural power.
--
-- >>> power (nat 2) (nat 10)
-- Natural 1024
--
-- >>> power (nat 3) (nat 0)
-- Natural 1
power ::
Natural ->
Natural ->
Natural
power (Natural x) (Natural y) =
Natural (x ^ y)
-- | Return the natural if the prism matches, otherwise zero.
--
-- >>> zeroOr (5 :: Integer)
-- Natural 5
--
-- >>> zeroOr (-1 :: Integer)
-- Natural 0
zeroOr ::
(AsNatural a) =>
a ->
Natural
zeroOr n =
fromMaybe zero' (n ^? _Natural)
-- | Count the elements in a foldable structure.
--
-- >>> length [1,2,3 :: Int]
-- Natural 3
--
-- >>> length ([] :: [Int])
-- Natural 0
length ::
(Foldable f) =>
f a ->
Natural
length =
foldl' (const . successor') zero'
-- | Replicate a value a natural number of times.
--
-- >>> replicate (nat 3) 'x'
-- "xxx"
--
-- >>> replicate (nat 0) 'x'
-- ""
replicate ::
Natural ->
a ->
[a]
replicate n =
take n . repeat
-- | Take the first n elements.
--
-- >>> take (nat 2) [1,2,3,4,5 :: Int]
-- [1,2]
--
-- >>> take (nat 0) [1,2,3 :: Int]
-- []
--
-- >>> take (nat 5) [1,2 :: Int]
-- [1,2]
take ::
Natural ->
[a] ->
[a]
take _ [] =
[]
take n (h : t) =
case n ^? successor of
Nothing ->
[]
Just p ->
h : take p t
-- | Drop the first n elements.
--
-- >>> drop (nat 2) [1,2,3,4,5 :: Int]
-- [3,4,5]
--
-- >>> drop (nat 0) [1,2,3 :: Int]
-- [1,2,3]
--
-- >>> drop (nat 5) [1,2 :: Int]
-- []
drop ::
Natural ->
[a] ->
[a]
drop _ [] =
[]
drop n (h : t) =
case n ^? successor of
Nothing ->
h : t
Just p ->
drop p t
-- | Split a list at position n.
--
-- >>> splitAt (nat 2) [1,2,3,4,5 :: Int]
-- ([1,2],[3,4,5])
splitAt ::
Natural ->
[a] ->
([a], [a])
splitAt n x =
(take n x, drop n x)
-- | Index into a list.
--
-- >>> [10,20,30 :: Int] !! nat 0
-- Just 10
--
-- >>> [10,20,30 :: Int] !! nat 2
-- Just 30
--
-- >>> [10,20,30 :: Int] !! nat 5
-- Nothing
--
-- >>> ([] :: [Int]) !! nat 0
-- Nothing
(!!) ::
[a] ->
Natural ->
Maybe a
[] !! _ =
Nothing
(h : t) !! n =
case n ^? successor of
Nothing ->
Just h
Just p ->
t !! p
-- | Find all indices where the predicate holds.
--
-- >>> findIndices (== 'a') "abacad"
-- [Natural 0,Natural 2,Natural 4]
--
-- >>> findIndices (== 'z') "abacad"
-- []
findIndices ::
(a -> Bool) ->
[a] ->
[Natural]
findIndices p x =
map snd (filter (p . fst) (zip x (iterate successor' zero')))
-- | Find the first index where the predicate holds.
--
-- >>> findIndex (== 'c') "abcde"
-- Just (Natural 2)
--
-- >>> findIndex (== 'z') "abcde"
-- Nothing
findIndex ::
(a -> Bool) ->
[a] ->
Maybe Natural
findIndex p =
listToMaybe . findIndices p
-- | Find all indices of a given element.
--
-- >>> elemIndices 'a' "banana"
-- [Natural 1,Natural 3,Natural 5]
elemIndices ::
(Eq a) =>
a ->
[a] ->
[Natural]
elemIndices =
findIndices . (==)
-- | Find the first index of a given element.
--
-- >>> elemIndex 'n' "banana"
-- Just (Natural 2)
--
-- >>> elemIndex 'z' "banana"
-- Nothing
elemIndex ::
(Eq a) =>
a ->
[a] ->
Maybe Natural
elemIndex =
findIndex . (==)
-- | Subtract two naturals, flooring at zero.
--
-- >>> minus (nat 5) (nat 3)
-- Natural 2
--
-- >>> minus (nat 3) (nat 5)
-- Natural 0
--
-- >>> minus (nat 3) (nat 3)
-- Natural 0
minus ::
Natural ->
Natural ->
Natural
minus (Natural x) (Natural y) =
Natural (if x < y then 0 else x - y)
-- | Iso between a natural and a list of units.
--
-- >>> nat 3 ^. list
-- [(),(),()]
--
-- >>> length (nat 3 ^. list)
-- Natural 3
list ::
Iso'
Natural
[()]
list =
iso
(`replicate` ())
length
----
-- | A positive integer (>= 1). Semigroup is multiplication.
--
-- >>> pos 3
-- Positive 3
--
-- >>> pos 3 <> pos 4
-- Positive 12
newtype Positive
= Positive
Integer
deriving (Eq, Ord, Show)
-- |
--
-- >>> pos 3 <> pos 4
-- Positive 12
instance Semigroup Positive where
Positive x <> Positive y =
Positive (x * y)
class HasPositive a where
positive ::
Lens'
a
Positive
-- |
--
-- >>> pos 5 ^. positive
-- Positive 5
instance HasPositive Positive where
positive =
id
class AsPositive a where
_Positive ::
Prism'
a
Positive
-- |
--
-- >>> _Positive # pos 5 :: Positive
-- Positive 5
instance AsPositive Positive where
_Positive =
id
integralPrism1 ::
(Integral a) =>
Prism'
a
Positive
integralPrism1 =
prism'
(\(Positive n) -> fromIntegral n)
(\n -> if n < 1 then Nothing else Just (Positive (fromIntegral n)))
-- |
--
-- >>> (5 :: Int) ^? _Positive
-- Just (Positive 5)
--
-- >>> (0 :: Int) ^? _Positive
-- Nothing
instance AsPositive Int where
_Positive =
integralPrism1
-- |
--
-- >>> (42 :: Integer) ^? _Positive
-- Just (Positive 42)
--
-- >>> (0 :: Integer) ^? _Positive
-- Nothing
instance AsPositive Integer where
_Positive =
integralPrism1
-- |
--
-- >>> (7 :: Word) ^? _Positive
-- Just (Positive 7)
--
-- >>> (0 :: Word) ^? _Positive
-- Nothing
instance AsPositive Word where
_Positive =
integralPrism1
-- |
--
-- >>> import Control.Applicative(Const(..))
-- >>> (Const 5 :: Const Integer Bool) ^? _Positive
-- Just (Positive 5)
instance (Integral a) => AsPositive (Const a b) where
_Positive =
integralPrism1
-- |
--
-- >>> import Data.Functor.Identity(Identity(..))
-- >>> (Identity 5 :: Identity Integer) ^? _Positive
-- Just (Positive 5)
instance (Integral a) => AsPositive (Identity a) where
_Positive =
integralPrism1
-- |
--
-- >>> SumPositive (pos 3) <> SumPositive (pos 4)
-- SumPositive (Positive 7)
newtype SumPositive
= SumPositive
Positive
deriving (Eq, Ord, Show)
-- |
--
-- >>> SumPositive (pos 5) ^. positive
-- Positive 5
instance HasPositive SumPositive where
positive =
_Wrapped . positive
-- |
--
-- >>> SumPositive (pos 5) ^? _Positive
-- Just (Positive 5)
instance AsPositive SumPositive where
_Positive =
_Wrapped . _Positive
instance
(SumPositive ~ a) =>
Rewrapped SumPositive a
-- |
--
-- >>> SumPositive (pos 5) ^. _Wrapped'
-- Positive 5
instance Wrapped SumPositive where
type Unwrapped SumPositive = Positive
_Wrapped' =
iso
(\(SumPositive x) -> x)
SumPositive
-- |
--
-- >>> SumPositive (pos 3) <> SumPositive (pos 4)
-- SumPositive (Positive 7)
instance Semigroup SumPositive where
SumPositive (Positive x) <> SumPositive (Positive y) =
SumPositive (Positive (x + y))
-- |
--
-- >>> MaxPositive (pos 3) <> MaxPositive (pos 7)
-- MaxPositive (Positive 7)
newtype MaxPositive
= MaxPositive
Positive
deriving (Eq, Ord, Show)
-- |
--
-- >>> MaxPositive (pos 7) ^. positive
-- Positive 7
instance HasPositive MaxPositive where
positive =
_Wrapped . positive
-- |
--
-- >>> MaxPositive (pos 7) ^? _Positive
-- Just (Positive 7)
instance AsPositive MaxPositive where
_Positive =
_Wrapped . _Positive
instance
(MaxPositive ~ a) =>
Rewrapped MaxPositive a
-- |
--
-- >>> MaxPositive (pos 7) ^. _Wrapped'
-- Positive 7
instance Wrapped MaxPositive where
type Unwrapped MaxPositive = Positive
_Wrapped' =
iso
(\(MaxPositive x) -> x)
MaxPositive
-- |
--
-- >>> MaxPositive (pos 3) <> MaxPositive (pos 7)
-- MaxPositive (Positive 7)
instance Semigroup MaxPositive where
MaxPositive (Positive x) <> MaxPositive (Positive y) =
MaxPositive (Positive (x `max` y))
-- |
--
-- >>> MinPositive (pos 3) <> MinPositive (pos 7)
-- MinPositive (Positive 3)
newtype MinPositive
= MinPositive
Positive
deriving (Eq, Ord, Show)
-- |
--
-- >>> MinPositive (pos 3) ^. positive
-- Positive 3
instance HasPositive MinPositive where
positive =
_Wrapped . positive
-- |
--
-- >>> MinPositive (pos 3) ^? _Positive
-- Just (Positive 3)
instance AsPositive MinPositive where
_Positive =
_Wrapped . _Positive
instance
(MinPositive ~ a) =>
Rewrapped MinPositive a
-- |
--
-- >>> MinPositive (pos 3) ^. _Wrapped'
-- Positive 3
instance Wrapped MinPositive where
type Unwrapped MinPositive = Positive
_Wrapped' =
iso
(\(MinPositive x) -> x)
MinPositive
-- |
--
-- >>> MinPositive (pos 3) <> MinPositive (pos 7)
-- MinPositive (Positive 3)
instance Semigroup MinPositive where
MinPositive (Positive x) <> MinPositive (Positive y) =
MinPositive (Positive (x `min` y))
-- | Serialises a 'Positive' to a JSON number.
--
-- >>> fromJSON (Number 1) :: Result Positive
-- Success (Positive 1)
--
-- >>> fromJSON (Number 42) :: Result Positive
-- Success (Positive 42)
--
-- >>> decode "42" :: Maybe Positive
-- Just (Positive 42)
--
-- >>> decode "1" :: Maybe Positive
-- Just (Positive 1)
--
-- >>> decode "0" :: Maybe Positive
-- Nothing
--
-- >>> decode "-1" :: Maybe Positive
-- Nothing
instance ToJSON Positive where
toJSON =
toJsonPositive
toEncoding (Positive n) =
toEncoding n
-- | Parses a 'Positive' from a JSON number, failing on non-positive values.
--
-- >>> decode "1" :: Maybe Positive
-- Just (Positive 1)
instance FromJSON Positive where
parseJSON =
parseJsonPositive
-- | Serialises any value with a 'HasPositive' instance to a JSON 'Value'.
--
-- >>> toJsonPositive (pos 42)
-- Number 42.0
--
-- >>> toJsonPositive (SumPositive (pos 7))
-- Number 7.0
--
-- >>> toJsonPositive (MaxPositive (pos 7))
-- Number 7.0
--
-- >>> toJsonPositive (MinPositive (pos 3))
-- Number 3.0
{-# SPECIALIZE toJsonPositive ::
Positive ->
Value
#-}
{-# INLINE toJsonPositive #-}
toJsonPositive ::
(HasPositive a) =>
a ->
Value
toJsonPositive a =
let Positive n = a ^. positive
in toJSON n
-- | Parses a JSON value into a 'Positive', failing on non-positive values.
--
-- >>> parse parseJsonPositive (Number 42)
-- Success (Positive 42)
--
-- >>> parse parseJsonPositive (Number 1)
-- Success (Positive 1)
--
-- >>> parse parseJsonPositive (Number 0)
-- Error "parse failed, Positive: expected positive integer"
--
-- >>> parse parseJsonPositive (Number (-1))
-- Error "parse failed, Positive: expected positive integer"
{-# INLINE parseJsonPositive #-}
parseJsonPositive ::
Value ->
Parser Positive
parseJsonPositive v =
parseJSON v >>= \n ->
if n < 1
then fail "parse failed, Positive: expected positive integer"
else pure (Positive n)
-- | Serialises a 'SumPositive' to a JSON number.
--
-- >>> encode (SumPositive (pos 7))
-- "7"
instance ToJSON SumPositive where
toJSON =
toJsonPositive
toEncoding (SumPositive n) =
toEncoding n
-- | Parses a 'SumPositive' from a JSON number, failing on non-positive values.
--
-- >>> decode "7" :: Maybe SumPositive
-- Just (SumPositive (Positive 7))
--
-- >>> decode "0" :: Maybe SumPositive
-- Nothing
instance FromJSON SumPositive where
parseJSON v =
parseJsonPositive v >>= \n -> pure (SumPositive n)
-- | Serialises a 'MaxPositive' to a JSON number.
--
-- >>> encode (MaxPositive (pos 7))
-- "7"
instance ToJSON MaxPositive where
toJSON =
toJsonPositive
toEncoding (MaxPositive n) =
toEncoding n
-- | Parses a 'MaxPositive' from a JSON number, failing on non-positive values.
--
-- >>> decode "7" :: Maybe MaxPositive
-- Just (MaxPositive (Positive 7))
--
-- >>> decode "0" :: Maybe MaxPositive
-- Nothing
instance FromJSON MaxPositive where
parseJSON v =
parseJsonPositive v >>= \n -> pure (MaxPositive n)
-- | Serialises a 'MinPositive' to a JSON number.
--
-- >>> encode (MinPositive (pos 3))
-- "3"
instance ToJSON MinPositive where
toJSON =
toJsonPositive
toEncoding (MinPositive n) =
toEncoding n
-- | Parses a 'MinPositive' from a JSON number, failing on non-positive values.
--
-- >>> decode "3" :: Maybe MinPositive
-- Just (MinPositive (Positive 3))
--
-- >>> decode "0" :: Maybe MinPositive
-- Nothing
instance FromJSON MinPositive where
parseJSON v =
parseJsonPositive v >>= \n -> pure (MinPositive n)
-- | Iso between a natural and maybe a positive.
--
-- >>> nat 5 ^. naturalPositive
-- Just (Positive 5)
--
-- >>> nat 0 ^. naturalPositive
-- Nothing
naturalPositive ::
Iso' Natural (Maybe Positive)
naturalPositive =
iso
( \(Natural n) ->
if n == 0 then Nothing else Just (Positive n)
)
( \x ->
Natural
( case x of
Nothing ->
0
Just (Positive n) ->
n
)
)
-- |
--
-- >>> nat 5 ^? _Positive
-- Just (Positive 5)
--
-- >>> nat 0 ^? _Positive
-- Nothing
instance AsPositive Natural where
_Positive =
prism'
(\(Positive n) -> Natural n)
(\(Natural n) -> if n == 0 then Nothing else Just (Positive n))
-- | Prism matching one.
--
-- >>> one # ()
-- Positive 1
--
-- >>> pos 1 ^? one
-- Just ()
--
-- >>> pos 3 ^? one
-- Nothing
one ::
Prism'
Positive
()
one =
prism'
(\() -> Positive 1)
(\(Positive n) -> if n == 1 then Just () else Nothing)
-- | The positive number one.
--
-- >>> one'
-- Positive 1
one' ::
Positive
one' =
one # ()
-- | Prism between a positive and its predecessor.
--
-- >>> successor1 # pos 1
-- Positive 2
--
-- >>> successor1 # pos 4
-- Positive 5
--
-- >>> pos 5 ^? successor1
-- Just (Positive 4)
--
-- >>> pos 1 ^? successor1
-- Nothing
successor1 ::
Prism'
Positive
Positive
successor1 =
prism'
(\(Positive n) -> Positive (n + 1))
(\(Positive n) -> if n == 1 then Nothing else Just (Positive (n - 1)))
-- | The successor of a positive number.
--
-- >>> successor1' (pos 1)
-- Positive 2
--
-- >>> successor1' (pos 4)
-- Positive 5
successor1' ::
Positive ->
Positive
successor1' =
(successor1 #)
-- | Iso between natural and positive (n <-> n+1).
--
-- >>> nat 0 ^. successorW
-- Positive 1
--
-- >>> nat 4 ^. successorW
-- Positive 5
--
-- >>> successorW # pos 1
-- Natural 0
--
-- >>> successorW # pos 5
-- Natural 4
successorW ::
Iso'
Natural
Positive
successorW =
iso
(\(Natural n) -> Positive (n + 1))
(\(Positive n) -> Natural (n - 1))
-- | Add two positive numbers.
--
-- >>> plus1 (pos 3) (pos 4)
-- Positive 7
plus1 ::
Positive ->
Positive ->
Positive
plus1 x y =
(_Wrapped # x <> (_Wrapped # y :: SumPositive)) ^. _Wrapped
-- | Multiply two positive numbers.
--
-- >>> multiply1 (pos 3) (pos 4)
-- Positive 12
multiply1 ::
Positive ->
Positive ->
Positive
multiply1 =
(<>)
-- | Raise a positive to a positive power.
--
-- >>> power1 (pos 2) (pos 10)
-- Positive 1024
--
-- >>> power1 (pos 3) (pos 2)
-- Positive 9
power1 ::
Positive ->
Positive ->
Positive
power1 (Positive x) (Positive y) =
Positive (x ^ y)
-- | Return the positive if the prism matches, otherwise one.
--
-- >>> oneOr (5 :: Integer)
-- Positive 5
--
-- >>> oneOr (0 :: Integer)
-- Positive 1
oneOr ::
(AsPositive a) =>
a ->
Positive
oneOr n =
fromMaybe one' (n ^? _Positive)
-- | Count the elements in a non-empty foldable.
--
-- >>> length1 ('a' :| "bc")
-- Positive 3
--
-- >>> length1 ('x' :| "")
-- Positive 1
length1 ::
(Foldable1 f) =>
f a ->
Positive
length1 x =
foldMap1 (const (SumPositive one')) x ^. _Wrapped
-- | Replicate a value a positive number of times.
--
-- >>> replicate1 (pos 3) 'x'
-- 'x' :| "xx"
--
-- >>> replicate1 (pos 1) 'y'
-- 'y' :| ""
replicate1 ::
Positive ->
a ->
NonEmpty a
replicate1 n a =
take1 n (a :| repeat a)
-- | Take the first n elements from a non-empty list.
--
-- >>> take1 (pos 2) (1 :| [2,3,4,5 :: Int])
-- 1 :| [2]
--
-- >>> take1 (pos 1) (1 :| [2,3 :: Int])
-- 1 :| []
take1 ::
Positive ->
NonEmpty a ->
NonEmpty a
take1 n (h :| t) =
h :| take (successorW # n) t
-- | Drop the first n elements from a non-empty list.
--
-- >>> drop1 (pos 2) (1 :| [2,3,4,5 :: Int])
-- [3,4,5]
--
-- >>> drop1 (pos 1) (1 :| [2,3 :: Int])
-- [2,3]
drop1 ::
Positive ->
NonEmpty a ->
[a]
drop1 n (_ :| t) =
drop (successorW # n) t
-- | Split a non-empty list at position n.
--
-- >>> splitAt1 (pos 2) (1 :| [2,3,4,5 :: Int])
-- (1 :| [2],[3,4,5])
splitAt1 ::
Positive ->
NonEmpty a ->
(NonEmpty a, [a])
splitAt1 n x =
(take1 n x, drop1 n x)
-- | Index into a non-empty list (1-based).
--
-- >>> (10 :| [20,30 :: Int]) !!! pos 1
-- Just 10
--
-- >>> (10 :| [20,30 :: Int]) !!! pos 3
-- Just 30
--
-- >>> (10 :| [20,30 :: Int]) !!! pos 5
-- Nothing
(!!!) ::
NonEmpty a ->
Positive ->
Maybe a
(h :| t) !!! n =
(h : t) !! (successorW # n)
-- | Find all 1-based indices where the predicate holds.
--
-- >>> findIndices1 (== 'a') ('a' :| "baca")
-- [Positive 1,Positive 3,Positive 5]
findIndices1 ::
(a -> Bool) ->
NonEmpty a ->
[Positive]
findIndices1 p x =
map snd (NonEmpty.filter (p . fst) (NonEmpty.zip x (NonEmpty.iterate successor1' one')))
-- | Find the first 1-based index where the predicate holds.
--
-- >>> findIndex1 (== 'c') ('a' :| "bcde")
-- Just (Positive 3)
--
-- >>> findIndex1 (== 'z') ('a' :| "bcde")
-- Nothing
findIndex1 ::
(a -> Bool) ->
NonEmpty a ->
Maybe Positive
findIndex1 p =
listToMaybe . findIndices1 p
-- | Find all 1-based indices of a given element.
--
-- >>> elemIndices1 'a' ('b' :| "anana")
-- [Positive 2,Positive 4,Positive 6]
elemIndices1 ::
(Eq a) =>
a ->
NonEmpty a ->
[Positive]
elemIndices1 =
findIndices1 . (==)
-- | Find the first 1-based index of a given element.
--
-- >>> elemIndex1 'n' ('b' :| "anana")
-- Just (Positive 3)
--
-- >>> elemIndex1 'z' ('b' :| "anana")
-- Nothing
elemIndex1 ::
(Eq a) =>
a ->
NonEmpty a ->
Maybe Positive
elemIndex1 =
findIndex1 . (==)
-- | Subtract two positives, flooring at one.
--
-- >>> minus1 (pos 5) (pos 3)
-- Positive 2
--
-- >>> minus1 (pos 3) (pos 5)
-- Positive 1
--
-- >>> minus1 (pos 3) (pos 3)
-- Positive 1
minus1 ::
Positive ->
Positive ->
Positive
minus1 (Positive x) (Positive y) =
Positive (if x <= y then 1 else x - y)
-- | Iso between a positive and a non-empty list of units.
--
-- >>> pos 3 ^. list1
-- () :| [(),()]
--
-- >>> length1 (pos 3 ^. list1)
-- Positive 3
list1 ::
Iso'
Positive
(NonEmpty ())
list1 =
iso
(`replicate1` ())
length1
-- | Convert natural to positive by adding one.
--
-- >>> plusone (nat 0)
-- Positive 1
--
-- >>> plusone (nat 4)
-- Positive 5
plusone ::
Natural ->
Positive
plusone =
(^. successorW)
-- | Convert positive to natural by subtracting one.
--
-- >>> minusone (pos 1)
-- Natural 0
--
-- >>> minusone (pos 5)
-- Natural 4
minusone ::
Positive ->
Natural
minusone =
(successorW #)
----
-- | A non-zero integer. 'True' for positive, 'False' for negative.
-- The 'Positive' gives the absolute value.
--
-- >>> NotZero True (pos 3)
-- NotZero True (Positive 3)
--
-- >>> NotZero False (pos 7)
-- NotZero False (Positive 7)
data NotZero
= NotZero
Bool
Positive
deriving (Eq, Show)
-- |
--
-- >>> compare (NotZero True (pos 3)) (NotZero True (pos 5))
-- LT
--
-- >>> compare (NotZero False (pos 3)) (NotZero False (pos 5))
-- GT
--
-- >>> compare (NotZero False (pos 1)) (NotZero True (pos 1))
-- LT
--
-- >>> compare (NotZero True (pos 1)) (NotZero False (pos 1))
-- GT
instance Ord NotZero where
compare (NotZero False (Positive x)) (NotZero False (Positive y)) = compare y x
compare (NotZero True (Positive x)) (NotZero True (Positive y)) = compare x y
compare (NotZero False _) (NotZero True _) = compare (0 :: Integer) (1 :: Integer)
compare (NotZero True _) (NotZero False _) = compare (1 :: Integer) (0 :: Integer)
-- | Semigroup under addition. Note: this is partial if the result would be zero.
-- Use 'plusNZ' for a total version.
--
-- >>> NotZero True (pos 3) <> NotZero True (pos 4)
-- NotZero True (Positive 7)
--
-- >>> NotZero False (pos 3) <> NotZero False (pos 4)
-- NotZero False (Positive 7)
--
-- >>> NotZero True (pos 5) <> NotZero False (pos 3)
-- NotZero True (Positive 2)
--
-- >>> NotZero False (pos 5) <> NotZero True (pos 3)
-- NotZero False (Positive 2)
instance Semigroup NotZero where
NotZero s1 (Positive x) <> NotZero s2 (Positive y) =
case (s1, s2) of
(True, True) -> NotZero True (Positive (x + y))
(False, False) -> NotZero False (Positive (x + y))
(True, False)
| x <= y -> NotZero False (Positive (y - x))
| True -> NotZero True (Positive (x - y))
(False, True)
| y <= x -> NotZero False (Positive (x - y))
| True -> NotZero True (Positive (y - x))
class HasNotZero a where
notZero ::
Lens'
a
NotZero
-- |
--
-- >>> NotZero True (pos 5) ^. notZero
-- NotZero True (Positive 5)
instance HasNotZero NotZero where
notZero =
id
-- |
--
-- >>> (7 :: Integer) ^? _NotZero
-- Just (NotZero True (Positive 7))
--
-- >>> (-3 :: Integer) ^? _NotZero
-- Just (NotZero False (Positive 3))
--
-- >>> (0 :: Integer) ^? _NotZero
-- Nothing
class AsNotZero a where
_NotZero ::
Prism'
a
NotZero
-- |
--
-- >>> _NotZero # NotZero True (pos 5) :: NotZero
-- NotZero True (Positive 5)
instance AsNotZero NotZero where
_NotZero =
id
integralPrismNZ ::
(Integral a) =>
Prism'
a
NotZero
integralPrismNZ =
prism'
(\(NotZero s (Positive n)) -> fromIntegral (if s then n else negate n))
( \n ->
let i = fromIntegral n :: Integer
in if i == 0
then Nothing
else Just (NotZero (i > 0) (Positive (abs i)))
)
where
(>) a b = not (a <= b) && not (a == b)
-- |
--
-- >>> (5 :: Int) ^? _NotZero
-- Just (NotZero True (Positive 5))
--
-- >>> (-5 :: Int) ^? _NotZero
-- Just (NotZero False (Positive 5))
--
-- >>> (0 :: Int) ^? _NotZero
-- Nothing
instance AsNotZero Int where
_NotZero =
integralPrismNZ
-- |
--
-- >>> (42 :: Integer) ^? _NotZero
-- Just (NotZero True (Positive 42))
--
-- >>> (-42 :: Integer) ^? _NotZero
-- Just (NotZero False (Positive 42))
--
-- >>> (0 :: Integer) ^? _NotZero
-- Nothing
instance AsNotZero Integer where
_NotZero =
integralPrismNZ
-- |
--
-- >>> (1 :: Word) ^? _NotZero
-- Just (NotZero True (Positive 1))
--
-- >>> (0 :: Word) ^? _NotZero
-- Nothing
instance AsNotZero Word where
_NotZero =
integralPrismNZ
-- |
--
-- >>> import Control.Applicative(Const(..))
-- >>> (Const 5 :: Const Integer Bool) ^? _NotZero
-- Just (NotZero True (Positive 5))
instance (Integral a) => AsNotZero (Const a b) where
_NotZero =
integralPrismNZ
-- |
--
-- >>> import Data.Functor.Identity(Identity(..))
-- >>> (Identity (-3) :: Identity Integer) ^? _NotZero
-- Just (NotZero False (Positive 3))
instance (Integral a) => AsNotZero (Identity a) where
_NotZero =
integralPrismNZ
-- |
--
-- >>> SumNotZero (NotZero True (pos 3)) <> SumNotZero (NotZero True (pos 4))
-- SumNotZero (NotZero True (Positive 7))
newtype SumNotZero
= SumNotZero
NotZero
deriving (Eq, Ord, Show)
-- |
--
-- >>> SumNotZero (NotZero True (pos 5)) ^. notZero
-- NotZero True (Positive 5)
instance HasNotZero SumNotZero where
notZero =
_Wrapped . notZero
-- |
--
-- >>> SumNotZero (NotZero True (pos 5)) ^? _NotZero
-- Just (NotZero True (Positive 5))
instance AsNotZero SumNotZero where
_NotZero =
_Wrapped . _NotZero
instance
(SumNotZero ~ a) =>
Rewrapped SumNotZero a
-- |
--
-- >>> SumNotZero (NotZero True (pos 5)) ^. _Wrapped'
-- NotZero True (Positive 5)
instance Wrapped SumNotZero where
type Unwrapped SumNotZero = NotZero
_Wrapped' =
iso
(\(SumNotZero x) -> x)
SumNotZero
-- |
--
-- >>> SumNotZero (NotZero True (pos 3)) <> SumNotZero (NotZero True (pos 4))
-- SumNotZero (NotZero True (Positive 7))
instance Semigroup SumNotZero where
SumNotZero x <> SumNotZero y =
SumNotZero (x <> y)
-- |
--
-- >>> MaxNotZero (NotZero True (pos 3)) <> MaxNotZero (NotZero True (pos 7))
-- MaxNotZero (NotZero True (Positive 7))
--
-- >>> MaxNotZero (NotZero False (pos 3)) <> MaxNotZero (NotZero True (pos 1))
-- MaxNotZero (NotZero True (Positive 1))
newtype MaxNotZero
= MaxNotZero
NotZero
deriving (Eq, Ord, Show)
-- |
--
-- >>> MaxNotZero (NotZero True (pos 7)) ^. notZero
-- NotZero True (Positive 7)
instance HasNotZero MaxNotZero where
notZero =
_Wrapped . notZero
-- |
--
-- >>> MaxNotZero (NotZero True (pos 7)) ^? _NotZero
-- Just (NotZero True (Positive 7))
instance AsNotZero MaxNotZero where
_NotZero =
_Wrapped . _NotZero
instance
(MaxNotZero ~ a) =>
Rewrapped MaxNotZero a
-- |
--
-- >>> MaxNotZero (NotZero True (pos 7)) ^. _Wrapped'
-- NotZero True (Positive 7)
instance Wrapped MaxNotZero where
type Unwrapped MaxNotZero = NotZero
_Wrapped' =
iso
(\(MaxNotZero x) -> x)
MaxNotZero
-- |
--
-- >>> MaxNotZero (NotZero True (pos 3)) <> MaxNotZero (NotZero True (pos 7))
-- MaxNotZero (NotZero True (Positive 7))
instance Semigroup MaxNotZero where
MaxNotZero x <> MaxNotZero y =
MaxNotZero (max x y)
-- |
--
-- >>> MinNotZero (NotZero True (pos 3)) <> MinNotZero (NotZero True (pos 7))
-- MinNotZero (NotZero True (Positive 3))
--
-- >>> MinNotZero (NotZero False (pos 3)) <> MinNotZero (NotZero True (pos 1))
-- MinNotZero (NotZero False (Positive 3))
newtype MinNotZero
= MinNotZero
NotZero
deriving (Eq, Ord, Show)
-- |
--
-- >>> MinNotZero (NotZero False (pos 3)) ^. notZero
-- NotZero False (Positive 3)
instance HasNotZero MinNotZero where
notZero =
_Wrapped . notZero
-- |
--
-- >>> MinNotZero (NotZero False (pos 3)) ^? _NotZero
-- Just (NotZero False (Positive 3))
instance AsNotZero MinNotZero where
_NotZero =
_Wrapped . _NotZero
instance
(MinNotZero ~ a) =>
Rewrapped MinNotZero a
-- |
--
-- >>> MinNotZero (NotZero False (pos 3)) ^. _Wrapped'
-- NotZero False (Positive 3)
instance Wrapped MinNotZero where
type Unwrapped MinNotZero = NotZero
_Wrapped' =
iso
(\(MinNotZero x) -> x)
MinNotZero
-- |
--
-- >>> MinNotZero (NotZero True (pos 3)) <> MinNotZero (NotZero True (pos 7))
-- MinNotZero (NotZero True (Positive 3))
instance Semigroup MinNotZero where
MinNotZero x <> MinNotZero y =
MinNotZero (min x y)
-- | Serialises a 'NotZero' to a JSON number.
--
-- >>> fromJSON (Number 5) :: Result NotZero
-- Success (NotZero True (Positive 5))
--
-- >>> fromJSON (Number (-3)) :: Result NotZero
-- Success (NotZero False (Positive 3))
--
-- >>> decode "7" :: Maybe NotZero
-- Just (NotZero True (Positive 7))
--
-- >>> decode "-2" :: Maybe NotZero
-- Just (NotZero False (Positive 2))
--
-- >>> decode "0" :: Maybe NotZero
-- Nothing
instance ToJSON NotZero where
toJSON =
toJsonNotZero
toEncoding nz =
toEncoding (notZeroInteger nz)
-- | Parses a 'NotZero' from a JSON number, failing on zero.
--
-- >>> decode "5" :: Maybe NotZero
-- Just (NotZero True (Positive 5))
instance FromJSON NotZero where
parseJSON =
parseJsonNotZero
-- | Serialises any value with a 'HasNotZero' instance to a JSON 'Value'.
--
-- >>> toJsonNotZero (NotZero True (pos 5))
-- Number 5.0
--
-- >>> toJsonNotZero (NotZero False (pos 3))
-- Number (-3.0)
--
-- >>> toJsonNotZero (SumNotZero (NotZero True (pos 7)))
-- Number 7.0
--
-- >>> toJsonNotZero (MaxNotZero (NotZero False (pos 2)))
-- Number (-2.0)
--
-- >>> toJsonNotZero (MinNotZero (NotZero True (pos 1)))
-- Number 1.0
{-# SPECIALIZE toJsonNotZero ::
NotZero ->
Value
#-}
{-# INLINE toJsonNotZero #-}
toJsonNotZero ::
(HasNotZero a) =>
a ->
Value
toJsonNotZero a =
toJSON (notZeroInteger (a ^. notZero))
-- | Parses a JSON value into a 'NotZero', failing on zero.
--
-- >>> parse parseJsonNotZero (Number 5)
-- Success (NotZero True (Positive 5))
--
-- >>> parse parseJsonNotZero (Number (-3))
-- Success (NotZero False (Positive 3))
--
-- >>> parse parseJsonNotZero (Number 0)
-- Error "parse failed, NotZero: expected non-zero integer"
{-# INLINE parseJsonNotZero #-}
parseJsonNotZero ::
Value ->
Parser NotZero
parseJsonNotZero v =
parseJSON v >>= \n ->
if n == 0
then fail "parse failed, NotZero: expected non-zero integer"
else
pure
( if n < 0
then NotZero False (Positive (negate n))
else NotZero True (Positive n)
)
-- | Serialises a 'SumNotZero' to a JSON number.
--
-- >>> encode (SumNotZero (NotZero True (pos 7)))
-- "7"
instance ToJSON SumNotZero where
toJSON =
toJsonNotZero
toEncoding (SumNotZero nz) =
toEncoding (notZeroInteger nz)
-- | Parses a 'SumNotZero' from a JSON number, failing on zero.
--
-- >>> decode "7" :: Maybe SumNotZero
-- Just (SumNotZero (NotZero True (Positive 7)))
--
-- >>> decode "0" :: Maybe SumNotZero
-- Nothing
instance FromJSON SumNotZero where
parseJSON v =
parseJsonNotZero v >>= \n -> pure (SumNotZero n)
-- | Serialises a 'MaxNotZero' to a JSON number.
--
-- >>> encode (MaxNotZero (NotZero False (pos 2)))
-- "-2"
instance ToJSON MaxNotZero where
toJSON =
toJsonNotZero
toEncoding (MaxNotZero nz) =
toEncoding (notZeroInteger nz)
-- | Parses a 'MaxNotZero' from a JSON number, failing on zero.
--
-- >>> decode "-2" :: Maybe MaxNotZero
-- Just (MaxNotZero (NotZero False (Positive 2)))
--
-- >>> decode "0" :: Maybe MaxNotZero
-- Nothing
instance FromJSON MaxNotZero where
parseJSON v =
parseJsonNotZero v >>= \n -> pure (MaxNotZero n)
-- | Serialises a 'MinNotZero' to a JSON number.
--
-- >>> encode (MinNotZero (NotZero True (pos 1)))
-- "1"
instance ToJSON MinNotZero where
toJSON =
toJsonNotZero
toEncoding (MinNotZero nz) =
toEncoding (notZeroInteger nz)
-- | Parses a 'MinNotZero' from a JSON number, failing on zero.
--
-- >>> decode "1" :: Maybe MinNotZero
-- Just (MinNotZero (NotZero True (Positive 1)))
--
-- >>> decode "0" :: Maybe MinNotZero
-- Nothing
instance FromJSON MinNotZero where
parseJSON v =
parseJsonNotZero v >>= \n -> pure (MinNotZero n)
-- | Embed a 'Positive' as a positive 'NotZero'.
--
-- >>> positiveNotZero (pos 5)
-- NotZero True (Positive 5)
positiveNotZero ::
Positive ->
NotZero
positiveNotZero =
NotZero True
-- | Embed a 'Positive' as a negative 'NotZero'.
--
-- >>> negativeNotZero (pos 5)
-- NotZero False (Positive 5)
negativeNotZero ::
Positive ->
NotZero
negativeNotZero =
NotZero False
-- | Extract the magnitude from a 'NotZero'.
--
-- >>> notZeroPositive (NotZero True (pos 5))
-- Positive 5
--
-- >>> notZeroPositive (NotZero False (pos 3))
-- Positive 3
notZeroPositive ::
NotZero ->
Positive
notZeroPositive (NotZero _ p) =
p
-- | Convert a 'NotZero' to an 'Integer'.
--
-- >>> notZeroInteger (NotZero True (pos 5))
-- 5
--
-- >>> notZeroInteger (NotZero False (pos 3))
-- -3
notZeroInteger ::
NotZero ->
Integer
notZeroInteger (NotZero s (Positive n)) =
if s then n else negate n
-- | Test if a 'NotZero' is positive.
--
-- >>> isPositive (NotZero True (pos 5))
-- True
--
-- >>> isPositive (NotZero False (pos 5))
-- False
isPositive ::
NotZero ->
Bool
isPositive (NotZero s _) =
s
-- | Test if a 'NotZero' is negative.
--
-- >>> isNegative (NotZero False (pos 5))
-- True
--
-- >>> isNegative (NotZero True (pos 5))
-- False
isNegative ::
NotZero ->
Bool
isNegative (NotZero s _) =
not s
-- | Negate a 'NotZero'.
--
-- >>> negateNZ (NotZero True (pos 5))
-- NotZero False (Positive 5)
--
-- >>> negateNZ (NotZero False (pos 3))
-- NotZero True (Positive 3)
negateNZ ::
NotZero ->
NotZero
negateNZ (NotZero s p) =
NotZero (not s) p
-- | Absolute value as a 'Positive'.
--
-- >>> absoluteNZ (NotZero True (pos 5))
-- Positive 5
--
-- >>> absoluteNZ (NotZero False (pos 3))
-- Positive 3
absoluteNZ ::
NotZero ->
Positive
absoluteNZ =
notZeroPositive
-- | Signum: positive one or negative one.
--
-- >>> signumNZ (NotZero True (pos 99))
-- NotZero True (Positive 1)
--
-- >>> signumNZ (NotZero False (pos 42))
-- NotZero False (Positive 1)
signumNZ ::
NotZero ->
NotZero
signumNZ (NotZero s _) =
NotZero s one'
-- | Add two 'NotZero' values. Returns 'Nothing' if the result is zero.
--
-- >>> plusNZ (NotZero True (pos 3)) (NotZero True (pos 4))
-- Just (NotZero True (Positive 7))
--
-- >>> plusNZ (NotZero True (pos 3)) (NotZero False (pos 3))
-- Nothing
--
-- >>> plusNZ (NotZero True (pos 5)) (NotZero False (pos 3))
-- Just (NotZero True (Positive 2))
--
-- >>> plusNZ (NotZero False (pos 5)) (NotZero True (pos 3))
-- Just (NotZero False (Positive 2))
plusNZ ::
NotZero ->
NotZero ->
Maybe NotZero
plusNZ (NotZero s1 (Positive x)) (NotZero s2 (Positive y)) =
case (s1, s2) of
(True, True) -> Just (NotZero True (Positive (x + y)))
(False, False) -> Just (NotZero False (Positive (x + y)))
(True, False)
| x == y -> Nothing
| x < y -> Just (NotZero False (Positive (y - x)))
| True -> Just (NotZero True (Positive (x - y)))
(False, True)
| x == y -> Nothing
| y < x -> Just (NotZero False (Positive (x - y)))
| True -> Just (NotZero True (Positive (y - x)))
-- | Multiply two 'NotZero' values. Always non-zero.
--
-- >>> multiplyNZ (NotZero True (pos 3)) (NotZero True (pos 4))
-- NotZero True (Positive 12)
--
-- >>> multiplyNZ (NotZero False (pos 3)) (NotZero True (pos 4))
-- NotZero False (Positive 12)
--
-- >>> multiplyNZ (NotZero False (pos 3)) (NotZero False (pos 4))
-- NotZero True (Positive 12)
multiplyNZ ::
NotZero ->
NotZero ->
NotZero
multiplyNZ (NotZero s1 (Positive x)) (NotZero s2 (Positive y)) =
NotZero (s1 == s2) (Positive (x * y))
-- | Return the 'NotZero' if the prism matches, otherwise positive one.
--
-- >>> notZeroOr (5 :: Integer)
-- NotZero True (Positive 5)
--
-- >>> notZeroOr (0 :: Integer)
-- NotZero True (Positive 1)
--
-- >>> notZeroOr (-3 :: Integer)
-- NotZero False (Positive 3)
notZeroOr ::
(AsNotZero a) =>
a ->
NotZero
notZeroOr n =
fromMaybe (NotZero True one') (n ^? _NotZero)
-- | Prism from 'NotZero' to 'Positive' (matches only positive values).
--
-- >>> (NotZero True (pos 5)) ^? _Positive
-- Just (Positive 5)
--
-- >>> (NotZero False (pos 5)) ^? _Positive
-- Nothing
instance AsPositive NotZero where
_Positive =
prism'
positiveNotZero
(\(NotZero s p) -> if s then Just p else Nothing)