functor-combinators-0.4.0.0: src/Data/Functor/Invariant/Inplicative.hs
-- |
-- Module : Data.Functor.Invariant.Inplicative
-- Copyright : (c) Justin Le 2021
-- License : BSD3
--
-- Maintainer : justin@jle.im
-- Stability : experimental
-- Portability : non-portable
--
-- Contains the classes 'Inply' and 'Inplicative', the invariant
-- counterparts to 'Apply'/'Divise' and 'Applicative'/'Divisible'.
--
-- @since 0.4.0.0
module Data.Functor.Invariant.Inplicative (
-- * Typeclass
Inply(..)
, Inplicative(..)
-- * Invariant 'Day'
, runDay
, dather
-- * Assembling Helpers
, concatInplicative
, concatInply
, concatInplicativeRec
, concatInplyRec
) where
import Control.Natural
import Data.Functor.Invariant
import Data.Functor.Invariant.Day
import Data.SOP hiding (hmap)
import qualified Data.Vinyl as V
import qualified Data.Vinyl.Functor as V
-- | The invariant counterpart of 'Apply' and 'Divise'.
--
-- Conceptually you can think of 'Apply' as, given a way to "combine" @a@ and
-- @b@ to @c@, lets you merge @f a@ (producer of @a@) and @f b@ (producer
-- of @b@) into a @f c@ (producer of @c@). 'Divise' can be thought of as,
-- given a way to "split" a @c@ into an @a@ and a @b@, lets you merge @f
-- a@ (consumer of @a@) and @f b@ (consumder of @b@) into a @f c@ (consumer
-- of @c@).
--
-- 'Inply', for 'gather', requires both a combining function and
-- a splitting function in order to merge @f b@ (producer and consumer of
-- @b@) and @f c@ (producer and consumer of @c@) into a @f a@. You can
-- think of it as, for the @f a@, it "splits" the a into @b@ and @c@ with
-- the @a -> (b, c)@, feeds it to the original @f b@ and @f c@, and then
-- re-combines the output back into a @a@ with the @b -> c -> a@.
--
-- @since 0.4.0.0
class Invariant f => Inply f where
-- | Like '<.>', '<*>', 'divise', or 'divide', but requires both
-- a splitting and a recombining function. '<.>' and '<*>' require
-- only a combining function, and 'divise' and 'divide' require only
-- a splitting function.
--
-- It is used to merge @f b@ (producer and consumer of @b@) and @f c@
-- (producer and consumer of @c@) into a @f a@. You can think of it
-- as, for the @f a@, it "splits" the a into @b@ and @c@ with the @a ->
-- (b, c)@, feeds it to the original @f b@ and @f c@, and then
-- re-combines the output back into a @a@ with the @b -> c -> a@.
--
-- An important property is that it will always use @both@ of the
-- ccomponents given in order to fulfil its job. If you gather an @f
-- a@ and an @f b@ into an @f c@, in order to consume/produdce the @c@,
-- it will always use both the @f a@ or the @f b@ -- exactly one of
-- them.
--
-- @since 0.4.0.0
gather
:: (b -> c -> a)
-> (a -> (b, c))
-> f b
-> f c
-> f a
gather f g x y = invmap (uncurry f) g (gathered x y)
-- | A simplified version of 'gather' that combines into a tuple. You
-- can then use 'invmap' to reshape it into the proper shape.
--
-- @since 0.4.0.0
gathered
:: f a
-> f b
-> f (a, b)
gathered = gather (,) id
{-# MINIMAL gather | gathered #-}
-- | The invariant counterpart of 'Applicative' and 'Divisible'.
--
-- The main important action is described in 'Inply', but this adds 'knot',
-- which is the counterpart to 'pure' and 'conquer'. It's the identity to
-- 'gather'; if combine two @f a@s with 'gather', and one of them is
-- 'knot', it will leave the structure unchanged.
--
-- Conceptually, if you think of 'gather' as "splitting and re-combining"
-- along multiple forks, then 'knot' introduces a fork that is never taken.
--
-- @since 0.4.0.0
class Inply f => Inplicative f where
knot :: a -> f a
-- | Interpret out of a contravariant 'Day' into any instance of 'Inply' by
-- providing two interpreting functions.
--
-- This should go in "Data.Functor.Invariant.Day", but that module is in
-- a different package.
--
-- @since 0.4.0.0
runDay
:: Inply h
=> (f ~> h)
-> (g ~> h)
-> Day f g ~> h
runDay f g (Day x y a b) = gather a b (f x) (g y)
-- | Squash the two items in a 'Day' using their natural 'Inply'
-- instances.
--
-- This should go in "Data.Functor.Invariant.Day", but that module is in
-- a different package.
--
-- @since 0.4.0.0
dather
:: Inply f
=> Day f f ~> f
dather (Day x y a b) = gather a b x y
-- | Convenient wrapper to build up an 'Inplicative' instance by providing
-- each component of it. This makes it much easier to build up longer
-- chains because you would only need to write the splitting/joining
-- functions in one place.
--
-- For example, if you had a data type
--
-- @
-- data MyType = MT Int Bool String
-- @
--
-- and an invariant functor and 'Inplicative' instance @Prim@
-- (representing, say, a bidirectional parser, where @Prim Int@ is
-- a bidirectional parser for an 'Int'@), then you could assemble
-- a bidirectional parser for a @MyType@ using:
--
-- @
-- invmap (\(MyType x y z) -> I x :* I y :* I z :* Nil)
-- (\(I x :* I y :* I z :* Nil) -> MyType x y z) $
-- concatInplicative $ intPrim
-- :* boolPrim
-- :* stringPrim
-- :* Nil
-- @
--
-- Some notes on usefulness depending on how many components you have:
--
-- * If you have 0 components, use 'knot' directly.
-- * If you have 1 component, use 'inject' directly.
-- * If you have 2 components, use 'gather' directly.
-- * If you have 3 or more components, these combinators may be useful;
-- otherwise you'd need to manually peel off tuples one-by-one.
--
-- @since 0.4.0.0
concatInplicative
:: Inplicative f
=> NP f as
-> f (NP I as)
concatInplicative = \case
Nil -> knot Nil
x :* xs -> gather
(\y ys -> I y :* ys)
(\case I y :* ys -> (y, ys))
x
(concatInplicative xs)
-- | A version of 'concatInplicative' for non-empty 'NP', but only
-- requiring an 'Inply' instance.
--
-- @since 0.4.0.0
concatInply
:: Inply f
=> NP f (a ': as)
-> f (NP I (a ': as))
concatInply (x :* xs) = case xs of
Nil -> invmap ((:* Nil) . I) (\case I y :* _ -> y) x
_ :* _ -> gather
(\y ys -> I y :* ys)
(\case I y :* ys -> (y, ys))
x
(concatInply xs)
-- | A version of 'concatInplicative' using 'V.XRec' from /vinyl/ instead of
-- 'NP' from /sop-core/. This can be more convenient because it doesn't
-- require manual unwrapping/wrapping of components.
--
-- @since 0.4.0.0
concatInplicativeRec
:: Inplicative f
=> V.Rec f as
-> f (V.XRec V.Identity as)
concatInplicativeRec = \case
V.RNil -> knot V.RNil
x V.:& xs -> gather
(V.::&)
(\case y V.::& ys -> (y, ys))
x
(concatInplicativeRec xs)
-- | A version of 'concatInply' using 'V.XRec' from /vinyl/ instead of
-- 'NP' from /sop-core/. This can be more convenient because it doesn't
-- require manual unwrapping/wrapping of components.
--
-- @since 0.4.0.0
concatInplyRec
:: Inply f
=> V.Rec f (a ': as)
-> f (V.XRec V.Identity (a ': as))
concatInplyRec (x V.:& xs) = case xs of
V.RNil -> invmap (V.::& V.RNil) (\case z V.::& _ -> z) x
_ V.:& _ -> gather
(V.::&)
(\case y V.::& ys -> (y, ys))
x
(concatInplyRec xs)