packages feed

functor-combinators-0.4.1.4: src/Control/Monad/Freer/Church.hs

-- |
-- Module      : Control.Monad.Freer.Church
-- Copyright   : (c) Justin Le 2025
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- The church-encoded "Freer" Monad.  Basically provides the free monad in
-- a way that is compatible with 'Data.Functor.HFunctor.HFunctor' and
-- 'Data.Functor.HFunctor.Interpret'.  We also have the "semigroup" version
-- 'Free1', which is the free  'Bind'.
--
-- The module also provides a version of 'GHC.Generics.:.:' (or
-- 'Data.Functor.Compose'), 'Comp', in a way that is compatible with
-- 'Data.Functor.Tensor.HBifunctor' and the related typeclasses.
module Control.Monad.Freer.Church (
  -- * 'Free'
  Free (..),
  reFree,

  -- ** Interpretation
  liftFree,
  interpretFree,
  retractFree,
  hoistFree,

  -- ** Folding
  foldFree,
  foldFree',
  foldFreeC,

  -- * 'Free1'
  Free1 (.., DoneF1, MoreF1),
  reFree1,
  toFree,

  -- ** Interpretation
  liftFree1,
  interpretFree1,
  retractFree1,
  hoistFree1,

  -- ** Conversion
  free1Comp,
  matchFree1,

  -- ** Folding
  foldFree1,
  foldFree1',
  foldFree1C,

  -- * 'Comp'
  Comp (.., Comp, unComp),
  comp,
) where

import Control.Applicative
import Control.Monad
import qualified Control.Monad.Free as M
import Control.Natural
import Data.Foldable
import Data.Functor
import Data.Functor.Bind
import Data.Functor.Classes
import Data.Functor.Coyoneda
import Data.Functor.Invariant
import Data.Functor.Plus
import Data.Pointed
import Data.Semigroup.Foldable
import Data.Semigroup.Traversable
import GHC.Generics
import Text.Read

-- | A @'Free' f@ is @f@ enhanced with "sequential binding" capabilities.
-- It allows you to sequence multiple @f@s one after the other, and also to
-- determine "what @f@ to sequence" based on the result of the computation
-- so far.
--
-- Essentially, you can think of this as "giving @f@ a 'Monad' instance",
-- with all that that entails ('return', '>>=', etc.).
--
-- Lift @f@ into it with @'Data.Functor.HFunctor.inject' :: f a -> Free
-- f a@.  When you finally want to "use" it, you can interpret it into any
-- monadic context:
--
-- @
-- 'Data.Functor.HFunctor.interpret'
--     :: 'Monad' g
--     => (forall x. f x -> g x)
--     -> 'Free' f a
--     -> g a
-- @
--
-- Structurally, this is equivalent to many "nested" f's.  A value of type
-- @'Free' f a@ is either:
--
-- *   @a@
-- *   @f a@
-- *   @f (f a)@
-- *   @f (f (f a))@
-- *   .. etc.
--
-- Under the hood, this is the Church-encoded Freer monad.  It's
-- 'Control.Monad.Free.Free', or 'Control.Monad.Free.Church.F', but in
-- a way that is compatible with 'Data.Functor.HFunctor.HFunctor' and
-- 'Data.Functor.HFunctor.Interpret'.
newtype Free f a = Free
  { runFree :: forall r. (a -> r) -> (forall s. f s -> (s -> r) -> r) -> r
  }

instance Functor (Free f) where
  fmap f x = Free $ \p b -> runFree x (p . f) b

-- | @since 0.4.1.2
instance Invariant (Free f) where
  invmap f _ = fmap f

instance Apply (Free f) where
  (<.>) = ap

instance Applicative (Free f) where
  pure x = Free $ \p _ -> p x
  (<*>) = (<.>)

instance Pointed (Free f) where
  point = pure

instance Bind (Free f) where
  x >>- f = Free $ \p b -> runFree x (\y -> runFree (f y) p b) b

instance Monad (Free f) where
  (>>=) = (>>-)

instance M.MonadFree f (Free f) where
  wrap x = Free $ \p b -> b x $ \y -> runFree y p b

instance Foldable f => Foldable (Free f) where
  foldMap f = foldFreeC f fold

instance Traversable f => Traversable (Free f) where
  traverse f =
    foldFree
      (fmap pure . f)
      (fmap M.wrap . sequenceA)

instance (Functor f, Eq1 f) => Eq1 (Free f) where
  liftEq eq x y = liftEq @(M.Free f) eq (reFree x) (reFree y)

instance (Functor f, Ord1 f) => Ord1 (Free f) where
  liftCompare c x y = liftCompare @(M.Free f) c (reFree x) (reFree y)

instance (Functor f, Eq1 f, Eq a) => Eq (Free f a) where
  (==) = eq1

instance (Functor f, Ord1 f, Ord a) => Ord (Free f a) where
  compare = compare1

instance (Functor f, Show1 f) => Show1 (Free f) where
  liftShowsPrec sp sl d x = case reFree x of
    M.Pure y -> showsUnaryWith sp "pure" d y
    M.Free ys -> showsUnaryWith (liftShowsPrec sp' sl') "wrap" d ys
    where
      sp' = liftShowsPrec sp sl
      sl' = liftShowList sp sl

-- | Show in terms of 'pure' and 'M.wrap'.
instance (Functor f, Show1 f, Show a) => Show (Free f a) where
  showsPrec = liftShowsPrec showsPrec showList

instance (Functor f, Read1 f) => Read1 (Free f) where
  liftReadsPrec rp rl = go
    where
      go =
        readsData $
          readsUnaryWith rp "pure" pure
            <> readsUnaryWith (liftReadsPrec go (liftReadList rp rl)) "wrap" M.wrap

-- | Read in terms of 'pure' and 'M.wrap'.
instance (Functor f, Read1 f, Read a) => Read (Free f a) where
  readPrec = readPrec1
  readListPrec = readListPrecDefault
  readList = readListDefault

-- | Convert a @'Free' f@ into any instance of @'M.MonadFree' f@.
reFree ::
  (M.MonadFree f m, Functor f) =>
  Free f a ->
  m a
reFree = foldFree pure M.wrap

-- | Lift an @f@ into @'Free' f@, so you can use it as a 'Monad'.
--
-- This is 'Data.HFunctor.inject'.
liftFree :: f ~> Free f
liftFree x = Free $ \p b -> b x p

-- | Interpret a @'Free' f@ into a context @g@, provided that @g@ has
-- a 'Monad' instance.
--
-- This is 'Data.HFunctor.Interpret.interpret'.
interpretFree :: Monad g => (f ~> g) -> Free f ~> g
interpretFree f = foldFree' pure ((>>=) . f)

-- | Extract the @f@s back "out" of a @'Free' f@, utilizing its 'Monad'
-- instance.
--
-- This is 'Data.HFunctor.Interpret.retract'.
retractFree :: Monad f => Free f ~> f
retractFree = foldFree' pure (>>=)

-- | Swap out the underlying functor over a 'Free'.  This preserves all of
-- the structure of the 'Free'.
hoistFree :: (f ~> g) -> Free f ~> Free g
hoistFree f x = Free $ \p b -> runFree x p (b . f)

-- | A version of 'foldFree' that doesn't require @'Functor' f@, by taking
-- a RankN folding function.  This is essentially a flipped 'runFree'.
foldFree' ::
  (a -> r) ->
  (forall s. f s -> (s -> r) -> r) ->
  Free f a ->
  r
foldFree' f g x = runFree x f g

-- | A version of 'foldFree' that doesn't require @'Functor' f@, by folding
-- over a 'Coyoneda' instead.
foldFreeC ::
  -- | handle 'pure'
  (a -> r) ->
  -- | handle 'M.wrap'
  (Coyoneda f r -> r) ->
  Free f a ->
  r
foldFreeC f g = foldFree' f (\y n -> g (Coyoneda n y))

-- | Recursively fold down a 'Free' by handling the 'pure' case and the
-- nested/wrapped case.
--
-- This is a catamorphism.
--
-- This requires @'Functor' f@; see 'foldFree'' and 'foldFreeC' for
-- a version that doesn't require @'Functor' f@.
foldFree ::
  Functor f =>
  -- | handle 'pure'
  (a -> r) ->
  -- | handle 'M.wrap'
  (f r -> r) ->
  Free f a ->
  r
foldFree f g = foldFreeC f (g . lowerCoyoneda)

-- | The Free 'Bind'.  Imbues any functor @f@ with a 'Bind' instance.
--
-- Conceptually, this is "'Free' without pure".  That is, while normally
-- @'Free' f a@ is an @a@, a @f a@, a @f (f a)@, etc., a @'Free1' f a@ is
-- an @f a@, @f (f a)@, @f (f (f a))@, etc.  It's a 'Free' with "at least
-- one layer of @f@", excluding the @a@ case.
--
-- It can be useful as the semigroup formed by ':.:' (functor composition):
-- Sometimes we want an @f :.: f@, or an @f :.: f :.: f@, or an @f :.:
-- f :.: f :.: f@...just as long as we have at least one @f@.
newtype Free1 f a = Free1
  { runFree1 ::
      forall r.
      (forall s. f s -> (s -> a) -> r) ->
      (forall s. f s -> (s -> r) -> r) ->
      r
  }

instance Functor (Free1 f) where
  fmap f x = Free1 $ \p b -> runFree1 x (\y c -> p y (f . c)) b

-- | @since 0.4.1.2
instance Invariant (Free1 f) where
  invmap f _ = fmap f

instance Apply (Free1 f) where
  (<.>) = apDefault

instance Bind (Free1 f) where
  x >>- f = Free1 $ \p b ->
    runFree1 x (\y c -> b y ((\q -> runFree1 q p b) . f . c)) b

instance Foldable f => Foldable (Free1 f) where
  foldMap f = foldFree1C (foldMap f) fold

instance Traversable f => Traversable (Free1 f) where
  traverse f =
    foldFree1
      (fmap DoneF1 . traverse f)
      (fmap MoreF1 . sequenceA)

instance Foldable1 f => Foldable1 (Free1 f) where
  foldMap1 f = foldFree1C (foldMap1 f) fold1

instance Traversable1 f => Traversable1 (Free1 f) where
  traverse1 f =
    foldFree1
      (fmap DoneF1 . traverse1 f)
      (fmap MoreF1 . sequence1)

instance (Functor f, Eq1 f) => Eq1 (Free1 f) where
  liftEq eq x y = liftEq @(Free f) eq (toFree x) (toFree y)

instance (Functor f, Ord1 f) => Ord1 (Free1 f) where
  liftCompare c x y = liftCompare @(Free f) c (toFree x) (toFree y)

instance (Functor f, Eq1 f, Eq a) => Eq (Free1 f a) where
  (==) = eq1

instance (Functor f, Ord1 f, Ord a) => Ord (Free1 f a) where
  compare = compare1

instance (Functor f, Show1 f) => Show1 (Free1 f) where
  liftShowsPrec sp sl d = \case
    DoneF1 x -> showsUnaryWith (liftShowsPrec sp sl) "DoneF1" d x
    MoreF1 x -> showsUnaryWith (liftShowsPrec sp' sl') "MoreF1" d x
    where
      sp' = liftShowsPrec sp sl
      sl' = liftShowList sp sl

-- | Show in terms of 'DoneF1' and 'MoreF1'.
instance (Functor f, Show1 f, Show a) => Show (Free1 f a) where
  showsPrec = liftShowsPrec showsPrec showList

instance (Functor f, Read1 f) => Read1 (Free1 f) where
  liftReadsPrec rp rl = go
    where
      go =
        readsData $
          readsUnaryWith (liftReadsPrec rp rl) "DoneF1" DoneF1
            <> readsUnaryWith (liftReadsPrec go (liftReadList rp rl)) "MoreF1" MoreF1

-- | Read in terms of 'DoneF1' and 'MoreF1'.
instance (Functor f, Read1 f, Read a) => Read (Free1 f a) where
  readPrec = readPrec1
  readListPrec = readListPrecDefault
  readList = readListDefault

-- | Constructor matching on the case that a @'Free1' f@ consists of just
-- a single un-nested @f@.  Used as a part of the 'Show' and 'Read'
-- instances.
pattern DoneF1 :: Functor f => f a -> Free1 f a
pattern DoneF1 x <- (matchFree1 -> L1 x)
  where
    DoneF1 x = liftFree1 x

-- | Constructor matching on the case that a @'Free1' f@ is a nested @f
-- ('Free1' f a)@.  Used as a part of the 'Show' and 'Read' instances.
--
-- As a constructor, this is equivalent to 'M.wrap'.
pattern MoreF1 :: Functor f => f (Free1 f a) -> Free1 f a
pattern MoreF1 x <- (matchFree1 -> R1 (Comp x))
  where
    MoreF1 x = liftFree1 x >>- id

{-# COMPLETE DoneF1, MoreF1 #-}

-- | Convert a @'Free1' f@ into any instance of @'M.MonadFree' f@.
reFree1 ::
  (M.MonadFree f m, Functor f) =>
  Free1 f a ->
  m a
reFree1 = foldFree1 (M.wrap . fmap pure) M.wrap

-- | @'Free1' f@ is a special subset of @'Free' f@ that consists of at least one
-- nested @f@.  This converts it back into the "bigger" type.
--
-- See 'free1Comp' for a version that preserves the "one nested layer"
-- property.
toFree :: Free1 f ~> Free f
toFree x = Free $ \p b -> runFree1 x (\y c -> b y (p . c)) b

-- | Map the underlying functor under a 'Free1'.
hoistFree1 :: (f ~> g) -> Free1 f ~> Free1 g
hoistFree1 f x = Free1 $ \p b -> runFree1 x (p . f) (b . f)

-- | Because a @'Free1' f@ is just a @'Free' f@ with at least one nested
-- layer of @f@, this function converts it back into the one-nested-@f@
-- format.
free1Comp :: Free1 f ~> Comp f (Free f)
free1Comp = foldFree1' (\y c -> y :>>= (pure . c)) $ \y n ->
  y :>>= \z -> case n z of
    q :>>= m -> liftFree q >>= m

-- | Inject an @f@ into a @'Free1' f@
liftFree1 :: f ~> Free1 f
liftFree1 x = Free1 $ \p _ -> p x id

-- | Retract the @f@ out of a @'Free1' f@, as long as the @f@ implements
-- 'Bind'.  Since we always have at least one @f@, we do not need a full
-- 'Monad' constraint.
retractFree1 :: Bind f => Free1 f ~> f
retractFree1 = foldFree1' (<&>) (>>-)

-- | Interpret the @'Free1' f@ in some context @g@, provided that @g@ has
-- a 'Bind' instance.  Since we always have at least one @f@, we will
-- always have at least one @g@, so we do not need a full 'Monad'
-- constraint.
interpretFree1 :: Bind g => (f ~> g) -> Free1 f ~> g
interpretFree1 f =
  foldFree1'
    (\y c -> c <$> f y)
    (\y n -> f y >>- n)

-- | A @'Free1' f@ is either a single un-nested @f@, or a @f@ nested with
-- another @'Free1' f@.  This decides which is the case.
matchFree1 :: forall f. Functor f => Free1 f ~> f :+: Comp f (Free1 f)
matchFree1 = foldFree1 L1 (R1 . Comp . fmap shuffle)
  where
    shuffle :: f :+: Comp f (Free1 f) ~> Free1 f
    shuffle (L1 y) = liftFree1 y
    shuffle (R1 (y :>>= n)) = liftFree1 y >>- n

-- | A version of 'foldFree1' that doesn't require @'Functor' f@, by taking
-- a RankN folding function.  This is essentially a flipped 'runFree'.
foldFree1' ::
  (forall s. f s -> (s -> a) -> r) ->
  (forall s. f s -> (s -> r) -> r) ->
  Free1 f a ->
  r
foldFree1' f g x = runFree1 x f g

-- | A version of 'foldFree1' that doesn't require @'Functor' f@, by
-- folding over a 'Coyoneda' instead.
foldFree1C ::
  (Coyoneda f a -> r) ->
  (Coyoneda f r -> r) ->
  Free1 f a ->
  r
foldFree1C f g =
  foldFree1'
    (\y c -> f (Coyoneda c y))
    (\y n -> g (Coyoneda n y))

-- | Recursively fold down a 'Free1' by handling the single @f@ case and
-- the nested/wrapped case.
--
-- This is a catamorphism.
--
-- This requires @'Functor' f@; see 'foldFree'' and 'foldFreeC' for
-- a version that doesn't require @'Functor' f@.
foldFree1 ::
  Functor f =>
  -- | handle @'DoneF1'@.
  (f a -> r) ->
  -- | handle @'MoreF1'@.
  (f r -> r) ->
  Free1 f a ->
  r
foldFree1 f g =
  foldFree1C
    (f . lowerCoyoneda)
    (g . lowerCoyoneda)

-- | Functor composition.  @'Comp' f g a@ is equivalent to @f (g a)@, and
-- the 'Comp' pattern synonym is a way of getting the @f (g a)@ in
-- a @'Comp' f g a@.
--
-- For example, @'Maybe' ('IO' 'Bool')@ is @'Comp' 'Maybe' 'IO' 'Bool'@.
--
-- This is mostly useful for its typeclass instances: in particular,
-- 'Functor', 'Applicative', 'Data.Functor.Tensor.HBifunctor', and
-- 'Data.Functor.Tensor.Monoidal'.
--
-- This is essentially a version of 'GHC.Generics.:.:' and
-- 'Data.Functor.Compose.Compose' that allows for an
-- 'Data.Functor.Tensor.HBifunctor' instance.
--
-- It is slightly less performant.  Using @'comp' . 'unComp'@ every once in
-- a while will concretize a 'Comp' value (if you have @'Functor' f@)
-- and remove some indirection if you have a lot of chained operations.
--
-- The "free monoid" over 'Comp' is 'Free', and the "free semigroup" over
-- 'Comp' is 'Free1'.
data Comp f g a
  = forall x. f x :>>= (x -> g a)

instance Functor g => Functor (Comp f g) where
  fmap f (x :>>= h) = x :>>= (fmap f . h)

-- | @since 0.4.1.2
instance Invariant g => Invariant (Comp f g) where
  invmap f g (x :>>= h) = x :>>= (invmap f g . h)

-- | @since 0.3.6.0
instance (Apply f, Apply g) => Apply (Comp f g) where
  (x :>>= f) <.> (y :>>= g) =
    ((,) <$> x <.> y)
      :>>= (\(x', y') -> f x' <.> g y')
  liftF2 h (x :>>= f) (y :>>= g) =
    ((,) <$> x <.> y)
      :>>= (\(x', y') -> liftF2 h (f x') (g y'))

instance (Applicative f, Applicative g) => Applicative (Comp f g) where
  pure x = pure () :>>= (pure . const x)
  (x :>>= f) <*> (y :>>= g) =
    ((,) <$> x <*> y)
      :>>= (\(x', y') -> f x' <*> g y')
  liftA2 h (x :>>= f) (y :>>= g) =
    ((,) <$> x <*> y)
      :>>= (\(x', y') -> liftA2 h (f x') (g y'))

instance (Foldable f, Foldable g) => Foldable (Comp f g) where
  foldMap f (x :>>= h) = foldMap (foldMap f . h) x

instance (Traversable f, Traversable g) => Traversable (Comp f g) where
  traverse f (x :>>= h) =
    (:>>= id)
      <$> traverse (traverse f . h) x

instance (Alternative f, Alternative g) => Alternative (Comp f g) where
  empty = empty :>>= id
  (x :>>= f) <|> (y :>>= g) = ((f <$> x) <|> (g <$> y)) :>>= id

-- | @since 0.3.6.0
instance (Alt f, Alt g) => Alt (Comp f g) where
  (x :>>= f) <!> (y :>>= g) = ((f <$> x) <!> (g <$> y)) :>>= id

-- | @since 0.3.6.0
instance (Plus f, Plus g) => Plus (Comp f g) where
  zero = zero :>>= id

instance (Functor f, Show1 f, Show1 g) => Show1 (Comp f g) where
  liftShowsPrec sp sl d (Comp x) =
    showsUnaryWith (liftShowsPrec sp' sl') "Comp" d x
    where
      sp' = liftShowsPrec sp sl
      sl' = liftShowList sp sl

instance (Functor f, Show1 f, Show1 g, Show a) => Show (Comp f g a) where
  showsPrec = liftShowsPrec showsPrec showList

instance (Functor f, Read1 f, Read1 g) => Read1 (Comp f g) where
  liftReadPrec rp rl =
    readData $
      readUnaryWith (liftReadPrec rp' rl') "Comp" Comp
    where
      rp' = liftReadPrec rp rl
      rl' = liftReadListPrec rp rl

instance (Functor f, Read1 f, Read1 g, Read a) => Read (Comp f g a) where
  readPrec = readPrec1
  readListPrec = readListPrecDefault
  readList = readListDefault

instance (Functor f, Eq1 f, Eq1 g) => Eq1 (Comp f g) where
  liftEq eq (Comp x) (Comp y) = liftEq (liftEq eq) x y

instance (Functor f, Ord1 f, Ord1 g) => Ord1 (Comp f g) where
  liftCompare c (Comp x) (Comp y) = liftCompare (liftCompare c) x y

instance (Functor f, Eq1 f, Eq1 g, Eq a) => Eq (Comp f g a) where
  (==) = eq1

instance (Functor f, Ord1 f, Ord1 g, Ord a) => Ord (Comp f g a) where
  compare = compare1

-- | "Smart constructor" for 'Comp' that doesn't require @'Functor' f@.
comp :: f (g a) -> Comp f g a
comp = (:>>= id)

-- | Pattern match on and construct a @'Comp' f g a@ as if it were @f
-- (g a)@.
pattern Comp :: Functor f => f (g a) -> Comp f g a
pattern Comp{unComp} <- (\case x :>>= f -> f <$> x -> unComp)
  where
    Comp x = comp x

{-# COMPLETE Comp #-}