{- | 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 #-}