mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 17:31:56 +03:00
429 lines
17 KiB
Haskell
429 lines
17 KiB
Haskell
{-# OPTIONS_GHC -Wno-inline-rule-shadowing -Wno-orphans #-} -- see Note [Arrow rewrite rules]
|
||
|
||
{-# LANGUAGE Arrows #-}
|
||
{-# LANGUAGE PatternSynonyms #-}
|
||
{-# LANGUAGE UndecidableInstances #-}
|
||
{-# LANGUAGE ViewPatterns #-}
|
||
|
||
-- | The missing standard library for arrows. Some of the functionality in this module is similar to
|
||
-- Paterson’s original @arrows@ library, but it has been modernized to work with recent versions of
|
||
-- GHC.
|
||
module Control.Arrow.Extended
|
||
( module Control.Arrow
|
||
, (>->)
|
||
, (<-<)
|
||
|
||
, foldlA'
|
||
, traverseA
|
||
, onNothingA
|
||
|
||
, ArrowTrans(..)
|
||
|
||
, ArrowKleisli(..)
|
||
, bindA
|
||
|
||
, ArrowError(..)
|
||
, mapErrorA
|
||
, ErrorA(..)
|
||
|
||
, ArrowReader(..)
|
||
, ReaderA(..)
|
||
|
||
, ArrowWriter(..)
|
||
, WriterA(WriterA, runWriterA)
|
||
) where
|
||
|
||
import Prelude hiding (id, (.))
|
||
|
||
import Control.Arrow
|
||
import Control.Category
|
||
import Control.Monad
|
||
import Control.Monad.Error.Class
|
||
import Control.Monad.Reader.Class
|
||
import Control.Monad.Writer.Class
|
||
import Data.Foldable
|
||
import Data.Tuple (swap)
|
||
|
||
infixl 1 >->
|
||
infixr 1 <-<
|
||
|
||
-- | The analog to '>>=' for arrow commands. In @proc@ notation, '>->' can be used to chain the
|
||
-- output of one command into the input of another.
|
||
--
|
||
-- See also Note [Weird control operator types].
|
||
(>->) :: (Arrow arr) => arr (e, s) a -> arr (e, (a, s)) b -> arr (e, s) b
|
||
f >-> g = proc (e, s) -> do
|
||
x <- f -< (e, s)
|
||
g -< (e, (x, s))
|
||
{-# INLINE (>->) #-}
|
||
|
||
(<-<) :: (Arrow arr) => arr (e, (a, s)) b -> arr (e, s) a -> arr (e, s) b
|
||
(<-<) = flip (>->)
|
||
{-# INLINE (<-<) #-}
|
||
|
||
-- | 'foldl'' lifted to arrows. See also Note [Weird control operator types].
|
||
foldlA' :: (ArrowChoice arr, Foldable t) => arr (e, (b, (a, s))) b -> arr (e, (b, (t a, s))) b
|
||
foldlA' f = arr (\(e, (v, (xs, s))) -> (e, (v, (toList xs, s)))) >>> go where
|
||
go = uncons >>> (id ||| step)
|
||
uncons = arr \(e, (v, (xs, s))) -> case xs of
|
||
[] -> Left v
|
||
x:xs' -> Right ((e, (v, (x, s))), (e, (xs', s)))
|
||
step = first f >>> arr (\(!v, (e, (xs, s))) -> (e, (v, (xs, s)))) >>> go
|
||
|
||
-- | An indexed version of Twan van Laarhoven’s @FunList@ type (see
|
||
-- <https://twanvl.nl/blog/haskell/non-regular1>). A value of type @'Traversal' a b (t b)@ is a
|
||
-- concrete representation of a traversal applied to a data structure of type @t a@ and producing a
|
||
-- value of type @t b@. This explicit representation is used to implement 'traverseA' using only
|
||
-- 'ArrowChoice'.
|
||
data Traversal a r b
|
||
= Done b
|
||
| Yield a !(r -> Traversal a r b)
|
||
|
||
instance Functor (Traversal a r) where
|
||
fmap f = \case
|
||
Done x -> Done (f x)
|
||
Yield v k -> Yield v (fmap f . k)
|
||
|
||
instance Applicative (Traversal a r) where
|
||
pure = Done
|
||
tf <*> tx = case tf of
|
||
Done f -> fmap f tx
|
||
Yield v k -> Yield v ((<*> tx) . k)
|
||
|
||
traversal :: (Traversable t) => t a -> Traversal a b (t b)
|
||
traversal = traverse (flip Yield Done)
|
||
|
||
-- | 'traverse' lifted to arrows. See also Note [Weird control operator types].
|
||
traverseA :: (ArrowChoice arr, Traversable t) => arr (e, (a, s)) b -> arr (e, (t a, s)) (t b)
|
||
traverseA f = second (first $ arr traversal) >>> go where
|
||
go = proc (e, (as, s)) -> case as of
|
||
Done bs -> returnA -< bs
|
||
Yield a k -> do
|
||
b <- f -< (e, (a, s))
|
||
go -< (e, (k b, s))
|
||
|
||
onNothingA :: (ArrowChoice arr) => arr (e, s) a -> arr (e, (Maybe a, s)) a
|
||
onNothingA f = proc (e, (v, s)) -> case v of
|
||
Just a -> returnA -< a
|
||
Nothing -> f -< (e, s)
|
||
|
||
{-# RULES -- These rules are missing from Control.Arrow; see Note [Arrow rewrite rules]
|
||
"arr/arr/R" forall f g h. arr f . (arr g . h) = arr (f . g) . h
|
||
|
||
"first/push" [~1] forall f g. first (f . g) = first f . first g
|
||
"second/push" [~1] forall f g. second (f . g) = second f . second g
|
||
"left/push" [~1] forall f g. left (f . g) = left f . left g
|
||
"right/push" [~1] forall f g. right (f . g) = right f . right g
|
||
|
||
"first/pull" [1] forall f g. first f . first g = first (f . g)
|
||
"second/pull" [1] forall f g. second f . second g = second (f . g)
|
||
"left/pull" [1] forall f g. left f . left g = left (f . g)
|
||
"right/pull" [1] forall f g. right f . right g = right (f . g)
|
||
#-}
|
||
|
||
-- | The class of /Kleisli arrows/, arrows made from monadic functions. Instances should satisfy
|
||
-- the following laws:
|
||
--
|
||
-- * @'arrM' ('pure' '.' /f/)@ ≡ @'arr' /f/@
|
||
-- * @('arrM' /f/ '>>>' 'arrM' /g/)@ ≡ @'arrM' (/f/ '>=>' /g/)@.
|
||
class (Monad m, Arrow arr) => ArrowKleisli m arr | arr -> m where
|
||
arrM :: (a -> m b) -> arr a b
|
||
|
||
{-# RULES -- see Note [Arrow rewrite rules]
|
||
"arr/arrM" forall f g. arr f . arrM g = arrM (fmap f . g)
|
||
"arrM/arr" forall f g. arrM f . arr g = arrM (f . g)
|
||
"arrM/arrM" forall f g. arrM f . arrM g = arrM (f <=< g)
|
||
|
||
"arr/arrM/R" forall f g h. arr f . (arrM g . h) = arrM (fmap f . g) . h
|
||
"arrM/arr/R" forall f g h. arrM f . (arr g . h) = arrM (f . g) . h
|
||
"arrM/arrM/R" forall f g h. arrM f . (arrM g . h) = arrM (f <=< g) . h
|
||
|
||
"first/arrM" forall f. first (arrM f) = arrM (runKleisli (first (Kleisli f)))
|
||
"second/arrM" forall f. second (arrM f) = arrM (runKleisli (second (Kleisli f)))
|
||
"left/arrM" forall f. left (arrM f) = arrM (runKleisli (left (Kleisli f)))
|
||
"right/arrM" forall f. right (arrM f) = arrM (runKleisli (right (Kleisli f)))
|
||
|
||
"***/arrM" forall f g. arrM f *** arrM g = arrM (runKleisli (Kleisli f *** Kleisli g))
|
||
"&&&/arrM" forall f g. arrM f &&& arrM g = arrM (runKleisli (Kleisli f &&& Kleisli g))
|
||
"+++/arrM" forall f g. arrM f +++ arrM g = arrM (runKleisli (Kleisli f +++ Kleisli g))
|
||
"|||/arrM" forall f g. arrM f ||| arrM g = arrM (runKleisli (Kleisli f ||| Kleisli g))
|
||
#-}
|
||
|
||
-- | A combinator that serves a similar role to 'returnA' in arrow notation, except that the
|
||
-- argument is a monadic action instead of a pure value. Just as 'returnA' is actually just
|
||
-- @'arr' 'id'@, 'ruleA' is just @'arrM' 'id'@, but it is provided as a separate function for
|
||
-- clarity.
|
||
--
|
||
-- 'bindA' is useful primarily because it allows executing a monadic action using arrow inputs
|
||
-- currently in scope. For example:
|
||
--
|
||
-- @
|
||
-- proc (a, b) -> do
|
||
-- x <- foo -< a
|
||
-- y <- bar -< b
|
||
-- 'bindA' -< f x y
|
||
-- @
|
||
--
|
||
-- The last statement is equivalent to @'arrM' ('uncurry' f) -< (x, y)@, but the use of 'bindA'
|
||
-- allows it to be expressed more directly.
|
||
bindA :: (ArrowKleisli m arr) => arr (m a) a
|
||
bindA = arrM id
|
||
{-# INLINE bindA #-}
|
||
|
||
instance (Monad m) => ArrowKleisli m (Kleisli m) where
|
||
arrM = Kleisli
|
||
|
||
class (Arrow arr, Arrow (t arr)) => ArrowTrans t arr where
|
||
liftA :: arr a b -> t arr a b
|
||
|
||
class (Arrow arr) => ArrowError e arr | arr -> e where
|
||
throwA :: arr e a
|
||
-- see Note [Weird control operator types]
|
||
catchA :: arr (a, s) b -> arr (a, (e, s)) b -> arr (a, s) b
|
||
|
||
mapErrorA :: (ArrowError e arr) => arr (a, s) b -> arr (a, ((e -> e), s)) b
|
||
mapErrorA f = proc (a, (g, s)) -> (f -< (a, s)) `catchA` \e -> throwA -< g e
|
||
|
||
class (Arrow arr) => ArrowReader r arr | arr -> r where
|
||
askA :: arr a r
|
||
-- see Note [Weird control operator types]
|
||
localA :: arr (a, s) b -> arr (a, (r, s)) b
|
||
|
||
class (Monoid w, Arrow arr) => ArrowWriter w arr | arr -> w where
|
||
tellA :: arr w ()
|
||
listenA :: arr a b -> arr a (b, w)
|
||
|
||
instance (MonadError e m) => ArrowError e (Kleisli m) where
|
||
throwA = Kleisli throwError
|
||
catchA (Kleisli f) (Kleisli g) = Kleisli \(a, s) -> f (a, s) `catchError` \e -> g (a, (e, s))
|
||
|
||
instance (MonadReader r m) => ArrowReader r (Kleisli m) where
|
||
askA = Kleisli $ const ask
|
||
localA (Kleisli f) = Kleisli \(a, (r, s)) -> local (const r) (f (a, s))
|
||
|
||
instance (MonadWriter w m) => ArrowWriter w (Kleisli m) where
|
||
tellA = Kleisli tell
|
||
listenA (Kleisli f) = Kleisli (listen . f)
|
||
|
||
newtype ErrorA e arr a b = ErrorA { runErrorA :: arr a (Either e b) }
|
||
deriving (Functor)
|
||
|
||
instance (ArrowChoice arr) => Category (ErrorA e arr) where
|
||
id = ErrorA (arr Right)
|
||
ErrorA f . ErrorA g = ErrorA ((arr Left ||| f) . g)
|
||
|
||
instance (ArrowChoice arr) => Arrow (ErrorA e arr) where
|
||
arr f = ErrorA (arr (Right . f))
|
||
first (ErrorA f) = ErrorA (arr (fmap swap . sequence . swap) . first f)
|
||
|
||
instance (ArrowChoice arr) => ArrowChoice (ErrorA e arr) where
|
||
left (ErrorA f) = ErrorA (arr (either (either Left (Right . Left)) (Right . Right)) . left f)
|
||
|
||
instance (ArrowChoice arr, ArrowApply arr) => ArrowApply (ErrorA e arr) where
|
||
app = ErrorA (app . first (arr runErrorA))
|
||
|
||
instance (ArrowChoice arr) => ArrowTrans (ErrorA e) arr where
|
||
liftA f = ErrorA (arr Right . f)
|
||
|
||
instance (ArrowChoice arr) => ArrowError e (ErrorA e arr) where
|
||
throwA = ErrorA (arr Left)
|
||
catchA (ErrorA f) (ErrorA g) = ErrorA proc (a, s) -> do
|
||
r <- f -< (a, s)
|
||
case r of
|
||
Left e -> g -< (a, (e, s))
|
||
Right v -> returnA -< Right v
|
||
|
||
instance (ArrowKleisli m arr, ArrowChoice arr) => ArrowKleisli m (ErrorA e arr) where
|
||
arrM = liftA . arrM
|
||
instance (ArrowReader r arr, ArrowChoice arr) => ArrowReader r (ErrorA e arr) where
|
||
askA = liftA askA
|
||
localA (ErrorA f) = ErrorA (localA f)
|
||
instance (ArrowWriter w arr, ArrowChoice arr) => ArrowWriter w (ErrorA e arr) where
|
||
tellA = liftA tellA
|
||
listenA (ErrorA f) = ErrorA (arr (\(r, w) -> (, w) <$> r) . listenA f)
|
||
|
||
newtype ReaderA r arr a b = ReaderA { runReaderA :: arr (a, r) b }
|
||
|
||
instance (Arrow arr) => Category (ReaderA r arr) where
|
||
id = ReaderA (arr fst)
|
||
ReaderA f . ReaderA g = ReaderA proc (a, r) -> do
|
||
b <- g -< (a, r)
|
||
f -< (b, r)
|
||
|
||
instance (Arrow arr) => Arrow (ReaderA r arr) where
|
||
arr f = ReaderA (arr (f . fst))
|
||
first (ReaderA f) = ReaderA proc ((a, c), r) -> do
|
||
b <- f -< (a, r)
|
||
returnA -< (b, c)
|
||
|
||
instance (ArrowChoice arr) => ArrowChoice (ReaderA r arr) where
|
||
left (ReaderA f) = ReaderA proc (e, r) -> case e of
|
||
Left a -> arr Left . f -< (a, r)
|
||
Right b -> returnA -< Right b
|
||
|
||
instance (ArrowApply arr) => ArrowApply (ReaderA r arr) where
|
||
app = ReaderA (app . arr \((ReaderA f, x), r) -> (f, (x, r)))
|
||
|
||
instance (Arrow arr) => ArrowTrans (ReaderA r) arr where
|
||
liftA f = ReaderA (f . arr fst)
|
||
|
||
instance (Arrow arr) => ArrowReader r (ReaderA r arr) where
|
||
askA = ReaderA (arr snd)
|
||
localA (ReaderA f) = ReaderA proc ((a, (r, s)), _) -> f -< ((a, s), r)
|
||
|
||
instance (ArrowKleisli m arr) => ArrowKleisli m (ReaderA r arr) where
|
||
arrM = liftA . arrM
|
||
instance (ArrowError e arr) => ArrowError e (ReaderA r arr) where
|
||
throwA = liftA throwA
|
||
catchA (ReaderA f) (ReaderA g) = ReaderA proc ((a, s), r) ->
|
||
(f -< ((a, s), r)) `catchA` \e -> g -< ((a, (e, s)), r)
|
||
instance (ArrowWriter w arr) => ArrowWriter w (ReaderA r arr) where
|
||
tellA = liftA tellA
|
||
listenA (ReaderA f) = ReaderA (listenA f)
|
||
|
||
newtype WriterA w arr a b
|
||
-- Internally defined using state passing to avoid space leaks. The real constructor should be
|
||
-- left unexported to avoid misuse.
|
||
= MkWriterA (arr (a, w) (b, w))
|
||
|
||
pattern WriterA :: (Monoid w, Arrow arr) => arr a (b, w) -> WriterA w arr a b
|
||
pattern WriterA { runWriterA } <- MkWriterA ((\f -> f . arr (, mempty)) -> runWriterA)
|
||
where
|
||
WriterA f = MkWriterA (arr (\((b, w), w1) -> let !w2 = w1 <> w in (b, w2)) . first f)
|
||
{-# COMPLETE WriterA #-}
|
||
|
||
instance (Category arr) => Category (WriterA w arr) where
|
||
id = MkWriterA id
|
||
MkWriterA f . MkWriterA g = MkWriterA (f . g)
|
||
|
||
instance (Arrow arr) => Arrow (WriterA w arr) where
|
||
arr f = MkWriterA (arr $ first f)
|
||
first (MkWriterA f) = MkWriterA proc ((a1, b), w1) -> do
|
||
(a2, w2) <- f -< (a1, w1)
|
||
returnA -< ((a2, b), w2)
|
||
|
||
instance (ArrowChoice arr) => ArrowChoice (WriterA w arr) where
|
||
left (MkWriterA f) = MkWriterA proc (e, w) -> case e of
|
||
Left a -> arr (first Left) . f -< (a, w)
|
||
Right b -> returnA -< (Right b, w)
|
||
|
||
instance (ArrowApply arr) => ArrowApply (WriterA w arr) where
|
||
app = MkWriterA (app . arr \((MkWriterA f, x), w) -> (f, (x, w)))
|
||
|
||
instance (Arrow arr) => ArrowTrans (WriterA w) arr where
|
||
liftA = MkWriterA . first
|
||
|
||
instance (Monoid w, Arrow arr) => ArrowWriter w (WriterA w arr) where
|
||
tellA = MkWriterA $ arr \(w, w1) -> let !w2 = w1 <> w in ((), w2)
|
||
listenA (WriterA f) = WriterA (arr (\(a, w) -> ((a, w), w)) . f)
|
||
|
||
instance (ArrowKleisli m arr) => ArrowKleisli m (WriterA w arr) where
|
||
arrM = liftA . arrM
|
||
instance (ArrowError e arr) => ArrowError e (WriterA w arr) where
|
||
throwA = liftA throwA
|
||
catchA (MkWriterA f) (MkWriterA g) = MkWriterA proc ((a, s), w) ->
|
||
(f -< ((a, s), w)) `catchA` \e -> g -< ((a, (e, s)), w)
|
||
instance (ArrowReader r arr) => ArrowReader r (WriterA w arr) where
|
||
askA = liftA askA
|
||
localA (MkWriterA f) = MkWriterA proc ((a, (r, s)), w) -> (| localA (f -< ((a, s), w)) |) r
|
||
|
||
{- Note [Weird control operator types]
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
Arrow notation (i.e. `proc`) has support for so-called “custom control operators,” which allow
|
||
things like
|
||
|
||
proc (x, y) -> do
|
||
z <- foo -< x
|
||
(f -< z) `catchA` \e -> g -< (y, e)
|
||
|
||
to magically work. What’s so magical about that? Well, note that `catchA` is an ordinary function,
|
||
but it’s being given /commands/ as arguments, not expressions. Also note that the arguments to
|
||
`catchA` reference the variables `y` and `z`, which are bound earlier in the `proc` expression as
|
||
arrow-local variables.
|
||
|
||
To make this work, GHC has to thread `y` and `z` through `catchA` in the generated code, which will
|
||
end up being something like this:
|
||
|
||
arr (\(x, y) -> (x, (x, y)))
|
||
>>> first foo
|
||
>>> arr (\(z, (x, y)) -> (z, y))
|
||
>>> catchA (first f)
|
||
(arr (\((_, y), e) -> (y, e)) >>> g)
|
||
|
||
Quite complicated, which is why we’re glad we don’t have to write it all out ourselves!
|
||
Unfortunately, since GHC 7.8, GHC has required some pretty stupid-looking types for control
|
||
operators to allow them to be used in `proc` notation. The natural type for `catchA` is
|
||
|
||
catchA :: arr a b -> arr (a, e) b -> arr a b
|
||
|
||
but GHC requires the far uglier
|
||
|
||
catchA :: arr (a, s) b -> arr (a, (e, s)) b -> arr (a, s) b
|
||
|
||
in order to make the type inference work out. I (Alexis) have submitted a GHC proposal to fix this
|
||
<https://github.com/ghc-proposals/ghc-proposals/pull/303>, so hopefully we’ll be able to use the
|
||
nicer type in the future (GHC 8.12 at the earliest). For now, though, we’ll have to use the ugly
|
||
version.
|
||
|
||
As of GHC 8.10, the way to read arrow control operator types is to look for arguments with a shape
|
||
like this:
|
||
|
||
arr (e, (a1, (a2, ... (an, s)))) b
|
||
|
||
The “actual” arguments to the arrow are the `a1` through `an` types, and the `e` and `s` types are
|
||
sort of “bookends.” So if you see a type like
|
||
|
||
arr (e, (Integer, (Char, (Bool, s)))) String
|
||
|
||
then you should read it as an arrow that takes three “arguments” of type `Integer`, `Char`, and
|
||
`Bool` and returns a `String`.
|
||
|
||
Stopping there is basically good enough, but if you want to know what’s really going on, the idea is
|
||
that each command in a `proc` block has an “environment” and an “argument stack,” represented by the
|
||
types `e` and `s`, respectively. The environment is used to thread arrow-local variables that are
|
||
currently in scope, and the argument stack (as the name implies) is used to pass the command
|
||
arguments. Control operators can push and pop things from this argument stack, and in the base case,
|
||
the empty argument stack is represented by `()`. For a full explanation, see the section of the GHC
|
||
User’s Guide on arrow notation:
|
||
|
||
https://downloads.haskell.org/ghc/8.8.1/docs/html/users_guide/glasgow_exts.html#arrow-notation
|
||
|
||
Yes, this all kind of sucks. Sorry.
|
||
|
||
|
||
Note [Arrow rewrite rules]
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
GHC’s desugaring of `proc` notation is not very clever, so it can generate some pretty inefficient
|
||
code. Almost everything is translated into uses of `arr`, `first`, and `(|||)`, and arrows end up
|
||
threading around massive tuples that constantly need to be packed and unpacked. To get good
|
||
performance, GHC relies on rewrite rules that expose optimizations to the simplifier, allowing the
|
||
packing and unpacking to be significantly reduced.
|
||
|
||
The most crucial rewrite rules are the ones for “`arr` fusion”, which rewrite expressions like
|
||
`arr f . arr g` into `arr (f . g)`. It might not be obvious at first why this is so important, but
|
||
remember that the arguments to `arr` are plain functions, not arrows. These functions might be
|
||
something like:
|
||
|
||
f (a, (b, c)) = ((a, b), c)
|
||
g ((a, _), c) = (a, c)
|
||
|
||
The composition of these functions `f . g` can be optimized to
|
||
|
||
f . g = \(a, (_, c)) -> (a, c)
|
||
|
||
skipping the intermediate tuple completely, but GHC can only do that if the two functions are
|
||
composed directly. If GHC only sees `arr f . arr g`, then it can’t assume anything about `arr`
|
||
(which might be overloaded), so it gets stuck.
|
||
|
||
The rewrite rules defined in Control.Category, Control.Arrow, and this module take advantage of
|
||
certain typeclass laws to enable many more optimizations to fire. However, there is a caveat to all
|
||
this: when GHC knows the concrete type of a particular arrow, it aggressively specializes uses of
|
||
`arr` and other operations to the concrete type. This process bypasses the rewrite rules completely.
|
||
|
||
GHC tries to warn us about this with the `-Winline-rule-shadowing` warning, but in this case, we
|
||
want the rules anyway, since they might fire on polymorphic code. However, the takeaway is that the
|
||
generic rules are not sufficient to get fast code: it’s important to /also/ define type-specific
|
||
rules in the event that GHC specializes concrete code. The good news is that those type-specific
|
||
rules can take advantage of type-specific optimizations, getting even better performance than would
|
||
be possible using the generic rules. The bad news is it’s a bit more work. -}
|