Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
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 PR shows how we can sometimes avoid using `Arrow`s 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 `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable 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 `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
{-# 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?
|
|
|
|
--
|
2022-04-04 15:35:10 +03:00
|
|
|
-- 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
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
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 PR shows how we can sometimes avoid using `Arrow`s 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 `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable 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 `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
-- 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
|
2022-04-04 15:35:10 +03:00
|
|
|
( interpretWriter,
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
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 PR shows how we can sometimes avoid using `Arrow`s 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 `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable 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 `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Control.Arrow
|
|
|
|
import Control.Arrow.Extended
|
2022-04-04 15:35:10 +03:00
|
|
|
import Control.Monad.Trans.Writer
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
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 PR shows how we can sometimes avoid using `Arrow`s 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 `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable 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 `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
|
2022-04-04 15:35:10 +03:00
|
|
|
-- | Translate a monadic writer effect stack of a computation into arrow-based
|
Avoid `Arrows` by interpreting monads
TL;DR
---
We go from this:
```haskell
(|
withRecordInconsistency
( (|
modifyErrA
( do
(info, dependencies) <- liftEitherA -< buildRelInfo relDef
recordDependencies -< (metadataObject, schemaObject, dependencies)
returnA -< info
)
|) (addTableContext @b table . addRelationshipContext)
)
|) metadataObject
```
to this:
```haskell
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 PR shows how we can sometimes avoid using `Arrow`s 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 `interpA` which interprets certain concrete monads such as `Writer w` into specific arrows, e.g. ones satisfying `ArrowWriter w`. This means that the part of the code that only uses such interpretable 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 `ArrowCache m`, implemented by the `Rule m` arrow. So code written with `ArrowCache m` in the context cannot be rewritten monadically using this technique.
See also
---
- #1827
- #2210
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3543
Co-authored-by: jkachmar <8461423+jkachmar@users.noreply.github.com>
GitOrigin-RevId: eb79619c95f7a571bce99bc144ce42ee65d08505
2022-02-22 21:08:54 +03:00
|
|
|
-- 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@.
|
2022-04-04 15:35:10 +03:00
|
|
|
interpretWriter :: ArrowWriter w arr => Writer w a `arr` a
|
|
|
|
interpretWriter = proc m -> do
|
|
|
|
let (a, w) = runWriter m
|
|
|
|
tellA -< w
|
|
|
|
returnA -< a
|