{-# language TypeOperators #-}

{-|
Coercive function composition.

__Note__: The functions in this module take an argument that solely
directs the /type/ of the coercion. The value of this argument is /ignored/.
In each case, this argument has a type that looks like @a \`to\` b@. As the name
of the @to@ type variable suggests, this will typically be a function from
@a@ to @b@. But leaving the type variable completely polymorphic and
unconstrained lets the type signature communicate the fact that the argument
is not used.
-}
module CoercibleUtils.Compose
  ( (#.)
  , (.#)
  ) 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 `to` 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 `to` b) -> a -> c
(.#) f _ = coerce f
{-# INLINE (.#) #-}