packages feed

monoidal-functors-0.3.0.0: src/Data/Functor/Monoidal/Specialized.hs

{-# LANGUAGE CPP #-}

module Data.Functor.Monoidal.Specialized
  ( -- * Applicative
    pure,
    liftA2,
    (<*>),
    (*>),
    (<*),

    -- * Alternative
    empty,
    (<|>),
    liftAlt2,

    -- * Semialign
    align,
    alignWith,

    -- * Divisible
    divide,
    conquer,
    divided,
    conquered,
    liftD,

    -- * Decidable
    lose,
    choose,
    chosen,
    lost,

    -- * Selection
    decide,
    branch,
    select,
  )
where

--------------------------------------------------------------------------------

import Data.Bifunctor.Monoidal.Specialized (merge')
import Data.Functor.Contravariant (Contravariant (..), Op (..))
import Data.Functor.Monoidal (Monoidal, Semigroupal (..), Unital (..))
import Data.These (These)
import Data.Void (Void, absurd)
#if MIN_VERSION_base(4,18,0)
import Prelude hiding ((<*>), (*>), (<*), liftA2, pure)
#else
import Prelude hiding ((<*>), (*>), (<*), pure)
#endif

--------------------------------------------------------------------------------

-- $setup
-- >>> :set -dppr-cols=1000
-- >>> import Prelude hiding (pure, (<*>), (*>), (<*))
-- >>> import Data.Functor.Contravariant (Predicate (..), getPredicate)
-- >>> import Data.Functor.Identity (Identity (..))
-- >>> import Data.These (These (..), these)
-- >>> import Data.Void (Void)

--------------------------------------------------------------------------------

-- | 'Control.Applicative.pure' specialized to a 'Unital' functor.
--
-- ==== __Examples__
--
-- >>> pure @Maybe (5 :: Int)
-- Just 5
pure :: (Functor f, Unital (->) () () f) => a -> f a
pure a = a <$ introduce @_ @() @() ()

-- | 'Control.Applicative.liftA2' specialized to a 'Semigroupal' functor.
liftA2 :: (Functor f, Semigroupal (->) (,) (,) f) => (a -> b -> c) -> f a -> f b -> f c
liftA2 f fa fb = uncurry f <$> combine (fa, fb)

infixl 4 <*>

-- | 'Control.Applicative.<*>' specialized to a 'Semigroupal' functor.
--
-- ==== __Examples__
--
-- >>> Just not <*> Just True
-- Just False
(<*>) :: (Functor f, Semigroupal (->) (,) (,) f) => f (a -> b) -> f a -> f b
ff <*> fa = liftA2 ($) ff fa

infixl 4 *>

-- | Sequence two actions, keeping the result of the second.
--
-- ==== __Examples__
--
-- >>> Just (1 :: Int) *> Just 2
-- Just 2
(*>) :: (Functor f, Semigroupal (->) (,) (,) f) => f a -> f b -> f b
(*>) = liftA2 (const id)

infixl 4 <*

-- | Sequence two actions, keeping the result of the first.
--
-- ==== __Examples__
--
-- >>> Just (1 :: Int) <* Just 2
-- Just 1
(<*) :: (Functor f, Semigroupal (->) (,) (,) f) => f a -> f b -> f a
(<*) = liftA2 const

--------------------------------------------------------------------------------

infixl 3 <|>

-- | 'Control.Applicative.<|>' specialized to a 'Semigroupal' functor over 'Either'.
--
-- ==== __Examples__
--
-- >>> Just (1 :: Int) <|> Just 2
-- Just 1
--
-- >>> (Nothing :: Maybe Int) <|> Just 2
-- Just 2
(<|>) :: (Functor f, Semigroupal (->) Either (,) f) => f a -> f a -> f a
fa <|> fb = liftAlt2 merge' fa fb

-- | 'Control.Applicative.empty' specialized to a 'Unital' functor over 'Void'.
--
-- ==== __Examples__
--
-- >>> empty :: Maybe Int
-- Nothing
empty :: forall f a. (Functor f, Unital (->) Void () f) => f a
empty = absurd <$> introduce @(->) @Void @() @f ()

-- | Combine two actions under a function of their sum.
--
-- ==== __Examples__
--
-- >>> liftAlt2 (either negate id) (Just (3 :: Int)) (Nothing :: Maybe Int)
-- Just (-3)
liftAlt2 :: (Functor f, Semigroupal (->) Either (,) f) => (Either a b -> c) -> f a -> f b -> f c
liftAlt2 f fa fb = f <$> combine (fa, fb)

--------------------------------------------------------------------------------

-- | 'Data.Align.align' specialized to a 'Semigroupal' functor over 'These'.
--
-- ==== __Examples__
--
-- >>> align (Just (1 :: Int)) (Just (2 :: Int))
-- Just (These 1 2)
--
-- >>> align (Just (1 :: Int)) (Nothing :: Maybe Int)
-- Just (This 1)
align :: (Functor f, Semigroupal (->) These (,) f) => f a -> f b -> f (These a b)
align = curry combine

-- | 'Data.Align.alignWith' specialized to a 'Semigroupal' functor over 'These'.
--
-- ==== __Examples__
--
-- >>> alignWith (these id id (+)) [1, 2 :: Int] [10, 20, 30]
-- [11,22,30]
alignWith :: (Functor f, Semigroupal (->) These (,) f) => (These a b -> c) -> f a -> f b -> f c
alignWith f fa fb = f <$> combine @_ @These (fa, fb)

--------------------------------------------------------------------------------

-- | 'Data.Functor.Contravariant.Divisible.divide' specialized to a 'Semigroupal' functor.
--
-- ==== __Examples__
--
-- >>> getPredicate (divide (\x -> (x, x)) (Predicate even) (Predicate (> 0))) (4 :: Int)
-- True
divide :: (Contravariant f, Semigroupal (->) (,) (,) f) => (c -> (a, b)) -> f a -> f b -> f c
divide f fa fb = contramap f $ combine (fa, fb)

-- | 'Data.Functor.Contravariant.Divisible.conquer' specialized to a 'Unital' functor.
--
-- ==== __Examples__
--
-- >>> getPredicate (conquer :: Predicate Int) 5
-- True
conquer :: (Contravariant f, Unital (->) () () f) => f a
conquer = contramap (const ()) conquered

-- | Combine two contravariant actions into one over their product.
--
-- ==== __Examples__
--
-- >>> getPredicate (divided (Predicate even) (Predicate (> 0))) (4 :: Int, 1 :: Int)
-- True
divided :: (Semigroupal (->) (,) (,) f) => f a -> f b -> f (a, b)
divided = curry combine

-- | The unit of 'divided'.
--
-- ==== __Examples__
--
-- >>> getPredicate conquered ()
-- True
conquered :: (Unital (->) () () f) => f ()
conquered = introduce ()

-- | Contravariantly map over a 'Monoidal' functor.
--
-- ==== __Examples__
--
-- >>> getPredicate (liftD (+ 1) (Predicate even)) (3 :: Int)
-- True
liftD :: (Contravariant f, Monoidal (->) (,) () (,) () f) => (a -> b) -> f b -> f a
liftD f = divide ((,) () . f) conquer

--------------------------------------------------------------------------------

-- | 'Data.Functor.Contravariant.Decidable.lose' specialized to a 'Unital' functor over 'Void'.
lose :: (Contravariant f, Unital (->) Void () f) => (a -> Void) -> f a
lose f = contramap f lost

-- | 'Data.Functor.Contravariant.Decidable.choose' specialized to a 'Semigroupal' functor over 'Either'.
--
-- ==== __Examples__
--
-- >>> getPredicate (choose (\n -> if even n then Left n else Right n) (Predicate (> 0)) (Predicate (< 0))) (4 :: Int)
-- True
choose :: (Contravariant f, Semigroupal (->) Either (,) f) => (c -> Either a b) -> f a -> f b -> f c
choose f fa fb = contramap f $ combine (fa, fb)

-- | Combine two contravariant actions into one over their sum.
--
-- ==== __Examples__
--
-- >>> getPredicate (chosen (Predicate even) (Predicate (> 0))) (Left 4 :: Either Int Int)
-- True
chosen :: (Contravariant f, Semigroupal (->) Either (,) f) => f b -> f c -> f (Either b c)
chosen = curry combine

-- | The unit of 'chosen'.
--
-- ==== __Examples__
--
-- >>> lost :: Maybe Void
-- Nothing
lost :: (Unital (->) Void () f) => f Void
lost = introduce ()

--------------------------------------------------------------------------------

-- | Pull a choice out of a functor.
--
-- ==== __Examples__
--
-- >>> decide (Identity (Left 5) :: Identity (Either Int Int))
-- Left (Identity 5)
decide :: (Functor f, Semigroupal Op Either Either f) => f (Either a b) -> Either (f a) (f b)
decide = getOp combine

-- | Branch on a choice, applying the matching function.
--
-- ==== __Examples__
--
-- >>> branch (Identity (Left 5) :: Identity (Either Int Int)) (Identity negate) (Identity id)
-- Identity (-5)
branch :: (Functor f, Semigroupal Op Either Either f, Semigroupal (->) (,) (,) f) => f (Either a b) -> f (a -> c) -> f (b -> c) -> f c
branch fab fac fbc =
  case decide fab of
    Left fa -> (\(a, f) -> f a) <$> combine (fa, fac)
    Right fb -> (\(b, f) -> f b) <$> combine (fb, fbc)

-- | Apply the function only when the value is a 'Left'.
--
-- ==== __Examples__
--
-- >>> select (Identity (Left 5) :: Identity (Either Int Int)) (Identity negate)
-- Identity (-5)
select :: (Functor f, Semigroupal Op Either Either f, Monoidal (->) (,) () (,) () f) => f (Either a b) -> f (a -> b) -> f b
select fa ff = branch fa ff (id <$ introduce @(->) @() ())