{- |
Primarily pulled from the
package @[newtype-generics](http://hackage.haskell.org/package/newtype-generics)@,
and based on Conor McBride's Epigram work, but
generalised to work over anything `Coercible`.
>>> ala Sum foldMap [1,2,3,4 :: Int] :: Int
10
>>> ala Endo foldMap [(+1), (+2), (subtract 1), (*2) :: Int -> Int] (3 :: Int) :: Int
8
>>> under2 Min (<>) 2 (1 :: Int) :: Int
1
>>> over All not (All False) :: All
All {getAll = True)
__Note__: All of the functions in this module take an argument that solely
directs the /type/ of the coercion. The value of this argument is /ignored/.
-}
module CoercibleUtils
( -- * Coercive composition
(#.), (.#)
-- * The classic "newtype" combinators
, op
, ala, ala'
, under, over
, under2, over2
, underF, overF
) where
import Data.Coerce (Coercible, coerce)
-- | Coercive left-composition.
--
-- >>> (All #. not) True
-- All {getAll = False}
--
-- The semantics with respect to bottoms are:
--
-- @
-- p '#.' ⊥ ≡ ⊥
-- p '#.' f ≡ p '.' f
-- @
infixr 9 #.
(#.) :: Coercible b c => (b -> c) -> (a -> b) -> a -> c
(#.) _ = coerce
{-# INLINE (#.) #-}
-- | Coercive right-composition.
--
-- >>> (stimes 2 .# Product) 3
-- Product {getProduct = 9}
--
-- The semantics with respect to bottoms are:
--
-- @
-- ⊥ '.#' p ≡ ⊥
-- f '.#' p ≡ p '.' f
-- @
infixr 9 .#
(.#) :: Coercible a b => (b -> c) -> (a -> b) -> a -> c
(.#) f _ = coerce f
{-# INLINE (.#) #-}
-- | Reverse the type of a "packer".
--
-- >>> op All (All True)
-- True
-- >>> op (Identity . Sum) (Identity (Sum 3))
-- 3
op :: Coercible a b
=> (a -> b)
-> b
-> a
op = coerce
{-# INLINE op #-}
-- | The workhorse of the package. Given a "packer" and a \"higher order function\" (/hof/),
-- it handles the packing and unpacking, and just sends you back a regular old
-- function, with the type varying based on the /hof/ you passed.
--
-- The reason for the signature of the /hof/ is due to 'ala' not caring about structure.
-- To illustrate why this is important, consider this alternative implementation of 'under2':
--
-- @
-- under2' :: (Coercible a b, Coercible a' b')
-- => (a -> b) -> (b -> b -> b') -> (a -> a -> a')
-- under2' pa f o1 o2 = 'ala' pa (\\p -> uncurry f . bimap p p) (o1, o2)
-- @
--
-- Being handed the "packer", the /hof/ may apply it in any structure of its choosing –
-- in this case a tuple.
--
-- >>> ala Sum foldMap [1,2,3,4 :: Int] :: Int
-- 10
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
ala pa hof = ala' pa hof id
{-# INLINE ala #-}
-- | The way it differs from the 'ala' function in this package,
-- is that it provides an extra hook into the \"packer\" passed to the hof.
--
-- However, this normally ends up being 'id', so 'ala' wraps this function and
-- passes 'id' as the final parameter by default.
-- If you want the convenience of being able to hook right into the /hof/,
-- you may use this function.
--
-- >>> ala' Sum foldMap length ["hello", "world"] :: Int
-- 10
--
-- >>> ala' First foldMap (readMaybe @Int) ["x", "42", "1"] :: Maybe Int
-- Just 42
ala' :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((d -> b) -> c -> b')
-> (d -> a)
-> c
-> a'
ala' _ hof f = coerce #. hof (coerce f)
{-# INLINE ala' #-}
-- | A very simple operation involving running the function /under/ the "packer".
--
-- >>> under Product (stimes 3) (3 :: Int) :: Int
-- 27
under :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> (b -> b')
-> a
-> a'
under _ f = coerce f
{-# INLINE under #-}
-- | The opposite of 'under'. I.e., take a function which works on the
-- underlying "unpacked" types, and switch it to a function that works
-- on the "packer".
--
-- >>> over All not (All False) :: All
-- All {getAll = True}
over :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> (a -> a')
-> b
-> b'
over _ f = coerce f
{-# INLINE over #-}
-- | Lower a binary function to operate on the underlying values.
--
-- >>> under2 Any (<>) True False :: Bool
-- True
under2 :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> (b -> b -> b')
-> a
-> a
-> a'
under2 _ f = coerce f
{-# INLINE under2 #-}
-- | The opposite of 'under2'.
over2 :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> (a -> a -> a')
-> b
-> b
-> b'
over2 _ f = coerce f
{-# INLINE over2 #-}
-- | 'under' lifted into a 'Functor'.
underF :: (Coercible a b, Coercible a' b', Functor f, Functor g)
=> (a -> b)
-> (f b -> g b')
-> f a
-> g a'
underF _ f = fmap coerce . f . fmap coerce
{-# INLINE underF #-}
-- | 'over' lifted into a 'Functor'.
overF :: (Coercible a b, Coercible a' b', Functor f, Functor g)
=> (a -> b)
-> (f a -> g a')
-> f b
-> g b'
overF _ f = fmap coerce . f . fmap coerce
{-# INLINE overF #-}