{-# LANGUAGE Arrows #-}

-- |
-- = TL;DR
--
-- We go from this:
--
-- > (|
-- >   withRecordInconsistency
-- >     ( (|
-- >         modifyErrA
-- >           ( do
-- >               (info, dependencies) <- liftEitherA -< buildRelInfo relDef
-- >               recordDependencies -< (metadataObject, schemaObject, dependencies)
-- >               returnA -< info
-- >           )
-- >       |) (addTableContext @b table . addRelationshipContext)
-- >     )
-- >   |) metadataObject
--
-- to this:
--
-- > withRecordInconsistencyM metadataObject $ do
-- >   modifyErr (addTableContext @b table . addRelationshipContext) $ do
-- >     (info, dependencies) <- liftEither $ buildRelInfo relDef
-- >     recordDependenciesM metadataObject schemaObject dependencies
-- >     return info
--
-- = Background
--
-- We use Haskell's @Arrows@ language extension to gain some syntactic sugar when
-- working with `Arrow`s. `Arrow`s are a programming abstraction comparable to
-- `Monad`s.
--
-- Unfortunately the syntactic sugar provided by this language extension is not
-- very sweet.
--
-- This module allows us to sometimes avoid using @Arrows@ syntax altogether,
-- without loss of functionality or correctness. It is a demo of a technique that
-- can be used to cut down the amount of @Arrows@-based code in our codebase by
-- about half.
--
-- = Approach
--
-- Although /in general/ not every `Monad` is an `Arrow`, specific `Arrow`
-- instantiations are exactly as powerful as their `Monad` equivalents. Otherwise
-- they wouldn't be very equivalent, would they?
--
-- Just like `liftEither` interprets the @`Either` e@ monad into an arbitrary
-- monad implementing @`MonadError` e@, we add interpret certain concrete monads
-- such as @`Control.Monad.Trans.Writer.CPS.Writer` w@ into arrows satisfying
-- constraints, in this example the ones satisfying @`ArrowWriter` w@.  This
-- means that the part of the code that only uses such interpretable arrow
-- effects can be written /monadically/, and then used in /arrow/ constructions
-- down the line.
--
-- This approach cannot be used for arrow effects which do not have a monadic
-- equivalent. In our codebase, the only instance of this is
-- @`Hasura.Incremental.ArrowCache` m@, implemented by the
-- @`Hasura.Incremental.Rule` m@ arrow. So code written with
-- @`Hasura.Incremental.ArrowCache` m@ in the context cannot be rewritten
-- monadically using this technique.
module Control.Arrow.Interpret
  ( interpretWriter,
  )
where

import Control.Arrow
import Control.Arrow.Extended
import Control.Monad.Trans.Writer

-- | Translate a monadic writer effect stack of a computation into arrow-based
-- effects.
--
-- NB: This is conceptually different from `ArrowKleisli`, which /inserts/ a
-- single monadic effect into an arrow-based effect stack.
--
-- NB: This is conceptually different from `ArrowApply`, which expresses that a
-- given `Arrow` /is/ a Kleisli arrow.  `ArrowInterpret` has no such condition
-- on @arr@.
interpretWriter :: ArrowWriter w arr => Writer w a `arr` a
interpretWriter :: arr (Writer w a) a
interpretWriter = proc Writer w a
m -> do
  let (a
a, w
w) = Writer w a -> (a, w)
forall w a. Writer w a -> (a, w)
runWriter Writer w a
m
  arr w ()
forall w (arr :: * -> * -> *). ArrowWriter w arr => arr w ()
tellA -< w
w
  arr a a
forall (a :: * -> * -> *) b. Arrow a => a b b
returnA -< a
a