natural-0.4.0.0: src/Natural.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Natural
( Natural,
HasNatural (..),
AsNatural (..),
ProductNatural (..),
MaxNatural (..),
MinNatural (..),
toJsonNatural,
parseJsonNatural,
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 (..),
toJsonPositive,
parseJsonPositive,
naturalPositive,
one,
one',
successor1,
successor1',
successorW,
plus1,
multiply1,
power1,
oneOr,
notZero,
length1,
replicate1,
take1,
drop1,
splitAt1,
(!!!),
findIndices1,
findIndex1,
elemIndices1,
elemIndex1,
minus1,
list1,
plusone,
minusone,
)
where
import Control.Applicative (Const, pure)
import Control.Category (id, (.))
import Control.Lens (Iso', Lens', Prism', Rewrapped, Wrapped (Unwrapped, _Wrapped'), iso, prism', view, (#), (^.), (^?), _Wrapped)
import Control.Monad ((>>=))
import Data.Aeson.Types
( FromJSON (parseJSON),
Parser,
ToJSON (toEncoding, toJSON),
Value,
)
import Data.Bool (Bool)
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 ((<), (<=)), 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, fail, fromIntegral, (*), (+), (-), (^))
-- $setup
-- >>> import Data.Aeson
-- >>> import Data.Aeson.Types (parse)
-- >>> :set -XOverloadedStrings
-- | A natural number (non-negative integer: 0, 1, 2, ...).
--
-- >>> Natural 0
-- Natural 0
--
-- >>> Natural 42
-- Natural 42
newtype Natural
= Natural
Integer
deriving (Eq, Ord, Show)
-- | Natural numbers form a semigroup under addition.
--
-- >>> Natural 3 <> Natural 4
-- Natural 7
instance Semigroup Natural where
Natural x <> Natural y =
Natural (x + y)
-- | The additive monoid of natural numbers with identity 0.
--
-- >>> mempty :: Natural
-- Natural 0
--
-- >>> mappend (Natural 3) (Natural 4)
-- Natural 7
instance Monoid Natural where
mappend =
(<>)
mempty =
Natural 0
-- | Typeclass for types that have a 'Natural' lens.
--
-- >>> Natural 5 ^. natural
-- Natural 5
class HasNatural a where
natural ::
Lens'
a
Natural
-- | >>> Natural 5 ^. natural
-- Natural 5
instance HasNatural Natural where
natural =
id
-- | Typeclass for types that can be converted to/from 'Natural' via a prism.
--
-- >>> (5 :: Integer) ^? _Natural
-- Just (Natural 5)
--
-- >>> (-1 :: Integer) ^? _Natural
-- Nothing
class AsNatural a where
_Natural ::
Prism'
a
Natural
-- | >>> Natural 5 ^? _Natural
-- Just (Natural 5)
instance AsNatural Natural where
_Natural =
id
-- | Prism for converting an integral value to a 'Natural'.
--
-- >>> (5 :: Integer) ^? integralPrism
-- Just (Natural 5)
--
-- >>> (-1 :: Integer) ^? integralPrism
-- Nothing
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
-- | >>> (5 :: Integer) ^? _Natural
-- Just (Natural 5)
--
-- >>> (-1 :: Integer) ^? _Natural
-- Nothing
instance AsNatural Integer where
_Natural =
integralPrism
-- | >>> (5 :: Word) ^? _Natural
-- Just (Natural 5)
instance AsNatural Word where
_Natural =
integralPrism
-- | >>> (Const 5 :: Const Integer Bool) ^? _Natural
-- Just (Natural 5)
instance (Integral a) => AsNatural (Const a b) where
_Natural =
integralPrism
-- | >>> (Identity 5 :: Identity Integer) ^? _Natural
-- Just (Natural 5)
instance (Integral a) => AsNatural (Identity a) where
_Natural =
integralPrism
-- | Natural numbers under multiplication.
--
-- >>> ProductNatural (Natural 3) <> ProductNatural (Natural 4)
-- ProductNatural (Natural 12)
--
-- >>> mempty :: ProductNatural
-- ProductNatural (Natural 1)
newtype ProductNatural
= ProductNatural
Natural
deriving (Eq, Ord, Show)
-- | >>> ProductNatural (Natural 5) ^. natural
-- Natural 5
instance HasNatural ProductNatural where
natural =
_Wrapped . natural
-- | >>> ProductNatural (Natural 5) ^? _Natural
-- Just (Natural 5)
instance AsNatural ProductNatural where
_Natural =
_Wrapped . _Natural
instance
(ProductNatural ~ a) =>
Rewrapped ProductNatural a
-- | >>> ProductNatural (Natural 5) ^. _Wrapped'
-- Natural 5
instance Wrapped ProductNatural where
type Unwrapped ProductNatural = Natural
_Wrapped' =
iso
(\(ProductNatural x) -> x)
ProductNatural
-- | >>> ProductNatural (Natural 3) <> ProductNatural (Natural 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)
-- | Natural numbers under maximum.
--
-- >>> MaxNatural (Natural 3) <> MaxNatural (Natural 7)
-- MaxNatural (Natural 7)
newtype MaxNatural
= MaxNatural
Natural
deriving (Eq, Ord, Show)
-- | >>> MaxNatural (Natural 5) ^. natural
-- Natural 5
instance HasNatural MaxNatural where
natural =
_Wrapped . natural
-- | >>> MaxNatural (Natural 5) ^? _Natural
-- Just (Natural 5)
instance AsNatural MaxNatural where
_Natural =
_Wrapped . _Natural
instance
(MaxNatural ~ a) =>
Rewrapped MaxNatural a
-- | >>> MaxNatural (Natural 5) ^. _Wrapped'
-- Natural 5
instance Wrapped MaxNatural where
type Unwrapped MaxNatural = Natural
_Wrapped' =
iso
(\(MaxNatural x) -> x)
MaxNatural
-- | >>> MaxNatural (Natural 3) <> MaxNatural (Natural 7)
-- MaxNatural (Natural 7)
instance Semigroup MaxNatural where
MaxNatural (Natural x) <> MaxNatural (Natural y) =
MaxNatural (Natural (x `max` y))
-- | Natural numbers under minimum.
--
-- >>> MinNatural (Natural 3) <> MinNatural (Natural 7)
-- MinNatural (Natural 3)
newtype MinNatural
= MinNatural
Natural
deriving (Eq, Ord, Show)
-- | >>> MinNatural (Natural 5) ^. natural
-- Natural 5
instance HasNatural MinNatural where
natural =
_Wrapped . natural
-- | >>> MinNatural (Natural 5) ^? _Natural
-- Just (Natural 5)
instance AsNatural MinNatural where
_Natural =
_Wrapped . _Natural
instance
(MinNatural ~ a) =>
Rewrapped MinNatural a
-- | >>> MinNatural (Natural 5) ^. _Wrapped'
-- Natural 5
instance Wrapped MinNatural where
type Unwrapped MinNatural = Natural
_Wrapped' =
iso
(\(MinNatural x) -> x)
MinNatural
-- | >>> MinNatural (Natural 3) <> MinNatural (Natural 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.
--
-- >>> toJSON (Natural 0)
-- Number 0.0
--
-- >>> toJSON (Natural 42)
-- Number 42.0
--
-- >>> encode (Natural 0)
-- "0"
--
-- >>> encode (Natural 42)
-- "42"
instance ToJSON Natural where
toJSON =
toJsonNatural
toEncoding (Natural n) =
toEncoding n
-- | Serialises any value with a 'HasNatural' instance to a JSON 'Value'.
--
-- >>> toJsonNatural (Natural 42)
-- Number 42.0
--
-- >>> toJsonNatural (ProductNatural (Natural 12))
-- Number 12.0
--
-- >>> toJsonNatural (MaxNatural (Natural 7))
-- Number 7.0
--
-- >>> toJsonNatural (MinNatural (Natural 3))
-- Number 3.0
{-# SPECIALIZE toJsonNatural ::
Natural ->
Value
#-}
{-# INLINE toJsonNatural #-}
toJsonNatural ::
(HasNatural a) =>
a ->
Value
toJsonNatural a =
let Natural n = view natural a
in toJSON n
-- | Parses a 'Natural' from a JSON number, failing on negative values.
--
-- >>> 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 FromJSON Natural where
parseJSON =
parseJsonNatural
-- | 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 (Natural 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 (Natural 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 (Natural 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)
-- | A prism for the zero natural number.
--
-- >>> zero # ()
-- Natural 0
--
-- >>> Natural 0 ^? zero
-- Just ()
--
-- >>> Natural 5 ^? 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 # ()
-- | A prism for the successor of a natural number.
--
-- >>> successor # Natural 0
-- Natural 1
--
-- >>> successor # Natural 4
-- Natural 5
--
-- >>> Natural 5 ^? successor
-- Just (Natural 4)
--
-- >>> Natural 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' (Natural 0)
-- Natural 1
--
-- >>> successor' (Natural 4)
-- Natural 5
successor' ::
Natural ->
Natural
successor' =
(successor #)
-- | Add two natural numbers.
--
-- >>> plus (Natural 3) (Natural 4)
-- Natural 7
--
-- >>> plus (Natural 0) (Natural 5)
-- Natural 5
plus ::
Natural ->
Natural ->
Natural
plus =
(<>)
-- | Multiply two natural numbers.
--
-- >>> multiply (Natural 3) (Natural 4)
-- Natural 12
--
-- >>> multiply (Natural 0) (Natural 5)
-- Natural 0
multiply ::
Natural ->
Natural ->
Natural
multiply x y =
(_Wrapped # x <> (_Wrapped # y :: ProductNatural)) ^. _Wrapped
-- | Raise a natural number to a power.
--
-- >>> power (Natural 2) (Natural 10)
-- Natural 1024
--
-- >>> power (Natural 5) (Natural 0)
-- Natural 1
power ::
Natural ->
Natural ->
Natural
power (Natural x) (Natural y) =
Natural (x ^ y)
-- | Convert to 'Natural', defaulting to zero if the value is not a valid natural.
--
-- >>> zeroOr (5 :: Integer)
-- Natural 5
--
-- >>> zeroOr (-1 :: Integer)
-- Natural 0
zeroOr ::
(AsNatural a) =>
a ->
Natural
zeroOr n =
fromMaybe zero' (n ^? _Natural)
-- | The length of a foldable structure as a 'Natural'.
--
-- >>> 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 (Natural 3) 'x'
-- "xxx"
--
-- >>> replicate (Natural 0) 'x'
-- ""
replicate ::
Natural ->
a ->
[a]
replicate n =
take n . repeat
-- | Take a natural number of elements from a list.
--
-- >>> take (Natural 2) [1, 2, 3 :: Int]
-- [1,2]
--
-- >>> take (Natural 0) [1, 2, 3 :: Int]
-- []
--
-- >>> take (Natural 5) [1, 2, 3 :: Int]
-- [1,2,3]
take ::
Natural ->
[a] ->
[a]
take _ [] =
[]
take n (h : t) =
case n ^? successor of
Nothing ->
[]
Just p ->
h : take p t
-- | Drop a natural number of elements from a list.
--
-- >>> drop (Natural 2) [1, 2, 3, 4 :: Int]
-- [3,4]
--
-- >>> drop (Natural 0) [1, 2, 3 :: Int]
-- [1,2,3]
--
-- >>> drop (Natural 5) [1, 2, 3 :: 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 a natural index.
--
-- >>> splitAt (Natural 2) [1, 2, 3, 4 :: Int]
-- ([1,2],[3,4])
--
-- >>> splitAt (Natural 0) [1, 2, 3 :: Int]
-- ([],[1,2,3])
--
-- >>> splitAt (Natural 5) [1, 2, 3 :: Int]
-- ([1,2,3],[])
splitAt ::
Natural ->
[a] ->
([a], [a])
splitAt n x =
(take n x, drop n x)
-- | Safe list indexing by 'Natural'.
--
-- >>> ([] :: [Int]) !! Natural 0
-- Nothing
--
-- >>> [1, 2, 3 :: Int] !! Natural 0
-- Just 1
(!!) ::
[a] ->
Natural ->
Maybe a
[] !! _ =
Nothing
(h : t) !! n = case n ^? successor of
Nothing -> Just h
Just p -> t !! p
-- | Find all indices where a predicate holds.
--
-- >>> findIndices (== 'a') "abacus"
-- [Natural 0,Natural 2]
--
-- >>> findIndices (== 'z') "abc"
-- []
findIndices ::
(a -> Bool) ->
[a] ->
[Natural]
findIndices p x =
map snd (filter (p . fst) (zip x (iterate successor' zero')))
-- | Find the first index where a predicate holds.
--
-- >>> findIndex (== 'a') "banana"
-- Just (Natural 1)
--
-- >>> findIndex (== 'z') "banana"
-- Nothing
findIndex ::
(a -> Bool) ->
[a] ->
Maybe Natural
findIndex p =
listToMaybe . findIndices p
-- | Find all indices of an element.
--
-- >>> elemIndices 'a' "abacus"
-- [Natural 0,Natural 2]
--
-- >>> elemIndices 'z' "abc"
-- []
elemIndices ::
(Eq a) =>
a ->
[a] ->
[Natural]
elemIndices =
findIndices . (==)
-- | Find the first index of an element.
--
-- >>> elemIndex 'a' "banana"
-- Just (Natural 1)
--
-- >>> elemIndex 'z' "banana"
-- Nothing
elemIndex ::
(Eq a) =>
a ->
[a] ->
Maybe Natural
elemIndex =
findIndex . (==)
-- | Subtract two natural numbers, flooring at zero.
--
-- >>> minus (Natural 5) (Natural 3)
-- Natural 2
--
-- >>> minus (Natural 3) (Natural 5)
-- Natural 0
minus ::
Natural ->
Natural ->
Natural
minus (Natural x) (Natural y) =
Natural (if x < y then 0 else x - y)
-- | An isomorphism between 'Natural' and @[()]@.
--
-- >>> Natural 3 ^. list
-- [(),(),()]
--
-- >>> Natural 0 ^. list
-- []
--
-- >>> list # [(), (), ()]
-- Natural 3
list ::
Iso'
Natural
[()]
list =
iso
(`replicate` ())
length
----
-- | A positive integer (strictly positive: 1, 2, 3, ...).
--
-- >>> Positive 1
-- Positive 1
--
-- >>> Positive 42
-- Positive 42
newtype Positive
= Positive
Integer
deriving (Eq, Ord, Show)
-- | Positive numbers form a semigroup under addition.
--
-- >>> Positive 3 <> Positive 4
-- Positive 7
instance Semigroup Positive where
Positive x <> Positive y =
Positive (x + y)
-- | Typeclass for types that have a 'Positive' lens.
--
-- >>> Positive 5 ^. positive
-- Positive 5
class HasPositive a where
positive ::
Lens'
a
Positive
-- | >>> Positive 5 ^. positive
-- Positive 5
instance HasPositive Positive where
positive =
id
-- | Typeclass for types that can be converted to/from 'Positive' via a prism.
--
-- >>> (5 :: Integer) ^? _Positive
-- Just (Positive 5)
--
-- >>> (0 :: Integer) ^? _Positive
-- Nothing
class AsPositive a where
_Positive ::
Prism'
a
Positive
-- | >>> Positive 5 ^? _Positive
-- Just (Positive 5)
instance AsPositive Positive where
_Positive =
id
-- | Prism for converting an integral value to a 'Positive'.
--
-- >>> (5 :: Integer) ^? integralPrism1
-- Just (Positive 5)
--
-- >>> (0 :: Integer) ^? integralPrism1
-- Nothing
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
-- | >>> (5 :: Integer) ^? _Positive
-- Just (Positive 5)
--
-- >>> (0 :: Integer) ^? _Positive
-- Nothing
instance AsPositive Integer where
_Positive =
integralPrism1
-- | >>> (5 :: Word) ^? _Positive
-- Just (Positive 5)
--
-- >>> (0 :: Word) ^? _Positive
-- Nothing
instance AsPositive Word where
_Positive =
integralPrism1
-- | >>> (Const 5 :: Const Integer Bool) ^? _Positive
-- Just (Positive 5)
instance (Integral a) => AsPositive (Const a b) where
_Positive =
integralPrism1
-- | >>> (Identity 5 :: Identity Integer) ^? _Positive
-- Just (Positive 5)
instance (Integral a) => AsPositive (Identity a) where
_Positive =
integralPrism1
-- | Positive numbers under addition.
--
-- >>> SumPositive (Positive 3) <> SumPositive (Positive 4)
-- SumPositive (Positive 7)
newtype SumPositive
= SumPositive
Positive
deriving (Eq, Ord, Show)
-- | >>> SumPositive (Positive 5) ^. positive
-- Positive 5
instance HasPositive SumPositive where
positive =
_Wrapped . positive
-- | >>> SumPositive (Positive 5) ^? _Positive
-- Just (Positive 5)
instance AsPositive SumPositive where
_Positive =
_Wrapped . _Positive
instance
(SumPositive ~ a) =>
Rewrapped SumPositive a
-- | >>> SumPositive (Positive 5) ^. _Wrapped'
-- Positive 5
instance Wrapped SumPositive where
type Unwrapped SumPositive = Positive
_Wrapped' =
iso
(\(SumPositive x) -> x)
SumPositive
-- | >>> SumPositive (Positive 3) <> SumPositive (Positive 4)
-- SumPositive (Positive 7)
instance Semigroup SumPositive where
SumPositive (Positive x) <> SumPositive (Positive y) =
SumPositive (Positive (x + y))
-- | Positive numbers under maximum.
--
-- >>> MaxPositive (Positive 3) <> MaxPositive (Positive 7)
-- MaxPositive (Positive 7)
newtype MaxPositive
= MaxPositive
Positive
deriving (Eq, Ord, Show)
-- | >>> MaxPositive (Positive 5) ^. positive
-- Positive 5
instance HasPositive MaxPositive where
positive =
_Wrapped . positive
-- | >>> MaxPositive (Positive 5) ^? _Positive
-- Just (Positive 5)
instance AsPositive MaxPositive where
_Positive =
_Wrapped . _Positive
instance
(MaxPositive ~ a) =>
Rewrapped MaxPositive a
-- | >>> MaxPositive (Positive 5) ^. _Wrapped'
-- Positive 5
instance Wrapped MaxPositive where
type Unwrapped MaxPositive = Positive
_Wrapped' =
iso
(\(MaxPositive x) -> x)
MaxPositive
-- | >>> MaxPositive (Positive 3) <> MaxPositive (Positive 7)
-- MaxPositive (Positive 7)
instance Semigroup MaxPositive where
MaxPositive (Positive x) <> MaxPositive (Positive y) =
MaxPositive (Positive (x `max` y))
-- | Positive numbers under minimum.
--
-- >>> MinPositive (Positive 3) <> MinPositive (Positive 7)
-- MinPositive (Positive 3)
newtype MinPositive
= MinPositive
Positive
deriving (Eq, Ord, Show)
-- | >>> MinPositive (Positive 5) ^. positive
-- Positive 5
instance HasPositive MinPositive where
positive =
_Wrapped . positive
-- | >>> MinPositive (Positive 5) ^? _Positive
-- Just (Positive 5)
instance AsPositive MinPositive where
_Positive =
_Wrapped . _Positive
instance
(MinPositive ~ a) =>
Rewrapped MinPositive a
-- | >>> MinPositive (Positive 5) ^. _Wrapped'
-- Positive 5
instance Wrapped MinPositive where
type Unwrapped MinPositive = Positive
_Wrapped' =
iso
(\(MinPositive x) -> x)
MinPositive
-- | >>> MinPositive (Positive 3) <> MinPositive (Positive 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.
--
-- >>> toJSON (Positive 1)
-- Number 1.0
--
-- >>> toJSON (Positive 42)
-- Number 42.0
--
-- >>> encode (Positive 1)
-- "1"
--
-- >>> encode (Positive 42)
-- "42"
instance ToJSON Positive where
toJSON =
toJsonPositive
toEncoding (Positive n) =
toEncoding n
-- | Serialises any value with a 'HasPositive' instance to a JSON 'Value'.
--
-- >>> toJsonPositive (Positive 42)
-- Number 42.0
--
-- >>> toJsonPositive (SumPositive (Positive 7))
-- Number 7.0
--
-- >>> toJsonPositive (MaxPositive (Positive 7))
-- Number 7.0
--
-- >>> toJsonPositive (MinPositive (Positive 3))
-- Number 3.0
{-# SPECIALIZE toJsonPositive ::
Positive ->
Value
#-}
{-# INLINE toJsonPositive #-}
toJsonPositive ::
(HasPositive a) =>
a ->
Value
toJsonPositive a =
let Positive n = view positive a
in toJSON n
-- | Parses a 'Positive' from a JSON number, failing on non-positive values.
--
-- >>> 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 FromJSON Positive where
parseJSON =
parseJsonPositive
-- | 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 (Positive 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 (Positive 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 (Positive 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)
-- | An isomorphism between 'Natural' and 'Maybe Positive'.
--
-- >>> Natural 0 ^. naturalPositive
-- Nothing
--
-- >>> Natural 5 ^. naturalPositive
-- Just (Positive 5)
--
-- >>> naturalPositive # Nothing
-- Natural 0
--
-- >>> naturalPositive # Just (Positive 3)
-- Natural 3
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
)
)
-- | >>> Natural 5 ^? _Positive
-- Just (Positive 5)
--
-- >>> Natural 0 ^? _Positive
-- Nothing
instance AsPositive Natural where
_Positive =
prism'
(\(Positive n) -> Natural n)
(\(Natural n) -> if n == 0 then Nothing else Just (Positive n))
-- | A prism for the value one.
--
-- >>> one # ()
-- Positive 1
--
-- >>> Positive 1 ^? one
-- Just ()
--
-- >>> Positive 5 ^? 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 # ()
-- | A prism for the successor of a positive number.
--
-- >>> successor1 # Positive 1
-- Positive 2
--
-- >>> successor1 # Positive 4
-- Positive 5
--
-- >>> Positive 5 ^? successor1
-- Just (Positive 4)
--
-- >>> Positive 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' (Positive 1)
-- Positive 2
--
-- >>> successor1' (Positive 4)
-- Positive 5
successor1' ::
Positive ->
Positive
successor1' =
(successor1 #)
-- | An isomorphism between 'Natural' and 'Positive' by adding/subtracting one.
--
-- >>> Natural 0 ^. successorW
-- Positive 1
--
-- >>> Natural 4 ^. successorW
-- Positive 5
--
-- >>> successorW # Positive 1
-- Natural 0
--
-- >>> successorW # Positive 5
-- Natural 4
successorW ::
Iso'
Natural
Positive
successorW =
iso
(\(Natural n) -> Positive (n + 1))
(\(Positive n) -> Natural (n - 1))
-- | Add two positive numbers.
--
-- >>> plus1 (Positive 3) (Positive 4)
-- Positive 7
--
-- >>> plus1 (Positive 1) (Positive 1)
-- Positive 2
plus1 ::
Positive ->
Positive ->
Positive
plus1 x y =
(_Wrapped # x <> (_Wrapped # y :: SumPositive)) ^. _Wrapped
-- | Multiply two positive numbers.
--
-- >>> multiply1 (Positive 2) (Positive 2)
-- Positive 4
--
-- >>> multiply1 (Positive 3) (Positive 4)
-- Positive 12
multiply1 ::
Positive ->
Positive ->
Positive
multiply1 (Positive x) (Positive y) =
Positive (x * y)
-- | Raise a positive number to a power.
--
-- >>> power1 (Positive 2) (Positive 10)
-- Positive 1024
--
-- >>> power1 (Positive 5) (Positive 1)
-- Positive 5
power1 ::
Positive ->
Positive ->
Positive
power1 (Positive x) (Positive y) =
Positive (x ^ y)
-- | Convert to 'Positive', defaulting to one if the value is not a valid positive.
--
-- >>> oneOr (5 :: Integer)
-- Positive 5
--
-- >>> oneOr (0 :: Integer)
-- Positive 1
oneOr ::
(AsPositive a) =>
a ->
Positive
oneOr n =
fromMaybe one' (n ^? _Positive)
-- | A prism from 'Natural' to 'Positive', succeeding when the natural is not zero.
--
-- >>> notZero # Positive 5
-- Natural 5
--
-- >>> Natural 5 ^? notZero
-- Just (Positive 5)
--
-- >>> Natural 0 ^? notZero
-- Nothing
notZero ::
Prism'
Natural
Positive
notZero =
prism'
(\(Positive n) -> Natural n)
(\(Natural n) -> if n == 0 then Nothing else Just (Positive n))
-- | The length of a non-empty foldable structure as a 'Positive'.
--
-- >>> length1 (1 :| [2, 3 :: Int])
-- Positive 3
--
-- >>> length1 (1 :| ([] :: [Int]))
-- Positive 1
length1 ::
(Foldable1 f) =>
f a ->
Positive
length1 x =
foldMap1 (const (SumPositive one')) x ^. _Wrapped
-- | Replicate a value a positive number of times into a 'NonEmpty'.
--
-- >>> replicate1 (Positive 3) 'x'
-- 'x' :| "xx"
--
-- >>> replicate1 (Positive 1) 'x'
-- 'x' :| ""
replicate1 ::
Positive ->
a ->
NonEmpty a
replicate1 n a =
take1 n (a :| repeat a)
-- | Take a positive number of elements from a 'NonEmpty'.
--
-- >>> take1 (Positive 2) (1 :| [2, 3 :: Int])
-- 1 :| [2]
--
-- >>> take1 (Positive 1) (1 :| [2, 3 :: Int])
-- 1 :| []
take1 ::
Positive ->
NonEmpty a ->
NonEmpty a
take1 n (h :| t) =
h :| take (successorW # n) t
-- | Drop a positive number of elements from a 'NonEmpty'.
--
-- >>> drop1 (Positive 1) (1 :| [2, 3 :: Int])
-- [2,3]
--
-- >>> drop1 (Positive 2) (1 :| [2, 3 :: Int])
-- [3]
drop1 ::
Positive ->
NonEmpty a ->
[a]
drop1 n (_ :| t) =
drop (successorW # n) t
-- | Split a 'NonEmpty' at a positive index.
--
-- >>> splitAt1 (Positive 2) (1 :| [2, 3, 4 :: Int])
-- (1 :| [2],[3,4])
--
-- >>> splitAt1 (Positive 1) (1 :| [2, 3, 4 :: Int])
-- (1 :| [],[2,3,4])
splitAt1 ::
Positive ->
NonEmpty a ->
(NonEmpty a, [a])
splitAt1 n x =
(take1 n x, drop1 n x)
-- | Safe indexing into a 'NonEmpty' by 'Positive'.
--
-- >>> (1 :| []) !!! Positive 1
-- Just 1
--
-- >>> (1 :| [2, 3 :: Int]) !!! Positive 1
-- Just 1
--
-- >>> (1 :| [2, 3 :: Int]) !!! Positive 4
-- Nothing
(!!!) ::
NonEmpty a ->
Positive ->
Maybe a
(h :| t) !!! n =
(h : t) !! (successorW # n)
-- | Find all indices where a predicate holds in a 'NonEmpty'.
--
-- >>> findIndices1 (== 'a') ('a' :| "bac")
-- [Positive 1,Positive 3]
--
-- >>> findIndices1 (== 'z') ('a' :| "bc")
-- []
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 index where a predicate holds in a 'NonEmpty'.
--
-- >>> findIndex1 (== 'a') ('b' :| "ana")
-- Just (Positive 2)
--
-- >>> findIndex1 (== 'z') ('b' :| "ana")
-- Nothing
findIndex1 ::
(a -> Bool) ->
NonEmpty a ->
Maybe Positive
findIndex1 p =
listToMaybe . findIndices1 p
-- | Find all indices of an element in a 'NonEmpty'.
--
-- >>> elemIndices1 'a' ('a' :| "bac")
-- [Positive 1,Positive 3]
--
-- >>> elemIndices1 'z' ('a' :| "bc")
-- []
elemIndices1 ::
(Eq a) =>
a ->
NonEmpty a ->
[Positive]
elemIndices1 =
findIndices1 . (==)
-- | Find the first index of an element in a 'NonEmpty'.
--
-- >>> elemIndex1 'a' ('b' :| "ana")
-- Just (Positive 2)
--
-- >>> elemIndex1 'z' ('b' :| "ana")
-- Nothing
elemIndex1 ::
(Eq a) =>
a ->
NonEmpty a ->
Maybe Positive
elemIndex1 =
findIndex1 . (==)
-- | Subtract two positive numbers, flooring at one.
--
-- >>> minus1 (Positive 5) (Positive 3)
-- Positive 2
--
-- >>> minus1 (Positive 3) (Positive 5)
-- Positive 1
minus1 ::
Positive ->
Positive ->
Positive
minus1 (Positive x) (Positive y) =
Positive (if x <= y then 1 else x - y)
-- | An isomorphism between 'Positive' and @'NonEmpty' ()@.
--
-- >>> Positive 3 ^. list1
-- () :| [(),()]
--
-- >>> Positive 1 ^. list1
-- () :| []
--
-- >>> list1 # (() :| [()])
-- Positive 2
list1 ::
Iso'
Positive
(NonEmpty ())
list1 =
iso
(`replicate1` ())
length1
-- | Convert a 'Natural' to its successor 'Positive'.
--
-- >>> plusone (Natural 0)
-- Positive 1
--
-- >>> plusone (Natural 4)
-- Positive 5
plusone ::
Natural ->
Positive
plusone =
(^. successorW)
-- | Convert a 'Positive' to its predecessor 'Natural'.
--
-- >>> minusone (Positive 1)
-- Natural 0
--
-- >>> minusone (Positive 5)
-- Natural 4
minusone ::
Positive ->
Natural
minusone =
(successorW #)