packages feed

proarrow-0.4.0.0: src/Proarrow/Tools/SMC/Internal/Do.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Internal module of "Proarrow.Tools.SMC": @do@ notation, including @rec@ blocks. It exports
-- everything, also what the public module keeps hidden.
module Proarrow.Tools.SMC.Internal.Do where

import Data.Kind (Constraint, Type)
import GHC.TypeNats (Nat, type (+))
import Proarrow.Category.Monoidal (Monoidal, MonoidalProfunctor, SymMonoidal, Tensor)
import Proarrow.Category.Monoidal qualified as M
import Proarrow.Category.Monoidal.Dialogue (Dialogue (..))
import Proarrow.Category.Monoidal.Strength (Costrong (..))
import Proarrow.Core (CAT, Hom, Profunctor (..), Promonad (..))
import Prelude (type (~))
import Prelude qualified as P

import Proarrow.Tools.SMC.Internal.Context
import Proarrow.Tools.SMC.Internal.Pattern
import Proarrow.Tools.SMC.Internal.Syntax
import Proarrow.Tools.SMC.Internal.Term

-- Do notation

-- | A bind in a @do@ block: a term taken apart by a pattern, or the variables of a @rec@ block.
type Bind :: forall {k}. CAT k -> Type -> Type -> Type -> Type -> Constraint
class Bind p m t cont r | m -> p where
  -- | Bind the right hand side to the pattern of the continuation.
  (>>=) :: m -> (t -> cont) -> r

-- | A term on the right hand side is taken apart by the pattern. Incoherent, so that it is chosen
-- as soon as the right hand side is known, unless the right hand side is a computation.
instance {-# INCOHERENT #-} (BindTerm p t (TermP p d g a) cont r) => Bind p (TermP p d g (a :: SYN k)) t cont r where
  {-# INLINE (>>=) #-}
  (>>=) = bindTerm @p @t

-- | A bind of a term, by its pattern. A variable pattern binds a new variable, so that the right
-- hand side is computed once however often the variable is used. The other patterns take the
-- right hand side apart into new variables already.
type BindTerm :: forall {k}. CAT k -> Type -> Type -> Type -> Type -> Constraint
class BindTerm p t m cont r where
  bindTerm :: m -> (t -> cont) -> r

-- The instances are incoherent, as for patterns: a variable pattern's type is often still unknown
-- when the instance is chosen.

instance
  {-# INCOHERENT #-}
  (Promonad p, MonoidalProfunctor p, Binds p d r a b t cont, Merge r g, r' ~ TermP p d (Union r g) b)
  => BindTerm p t (TermP p d g (a :: SYN k)) cont r'
  where
  {-# INLINE bindTerm #-}
  bindTerm (MkTerm m) k =
    withCtxOb @r (MkTerm (bound @p @d @r @a @b k . lmap (merge @r @g) (id @p @(Interp (Mul r)) M.** m)))

instance
  {-# INCOHERENT #-}
  (BindPat p (TermP p d g a) (x, y) cont r)
  => BindTerm p (x, y) (TermP p d g (a :: SYN k)) cont r
  where
  {-# INLINE bindTerm #-}
  bindTerm = bindPat

instance
  {-# INCOHERENT #-}
  (BindPat p (TermP p d g a) (x, y, z) cont r)
  => BindTerm p (x, y, z) (TermP p d g (a :: SYN k)) cont r
  where
  {-# INLINE bindTerm #-}
  bindTerm = bindPat

instance
  {-# INCOHERENT #-}
  (BindPat p (TermP p d g a) (w, x, y, z) cont r)
  => BindTerm p (w, x, y, z) (TermP p d g (a :: SYN k)) cont r
  where
  {-# INLINE bindTerm #-}
  bindTerm = bindPat

instance
  {-# INCOHERENT #-}
  (BindPat p (TermP p d g a) (v, w, x, y, z) cont r)
  => BindTerm p (v, w, x, y, z) (TermP p d g (a :: SYN k)) cont r
  where
  {-# INLINE bindTerm #-}
  bindTerm = bindPat

instance
  {-# INCOHERENT #-}
  (BindPat p (TermP p d g a) (u, v, w, x, y, z) cont r)
  => BindTerm p (u, v, w, x, y, z) (TermP p d g (a :: SYN k)) cont r
  where
  {-# INLINE bindTerm #-}
  bindTerm = bindPat

instance
  {-# INCOHERENT #-}
  (BindPat p (TermP p d g a) () cont r)
  => BindTerm p () (TermP p d g (a :: SYN k)) cont r
  where
  {-# INLINE bindTerm #-}
  bindTerm = bindPat

-- An explicit consumer determines what it consumes. An embedded dual object instead
-- uses the negative result expected by the surrounding computation.
type Negative :: forall {k}. SYN k -> SYN k -> Constraint
class (Interp c ~ Dual (Interp y)) => Negative c y

instance (y ~ y') => Negative (Not y') y
instance (f ~ Dual (Interp y)) => Negative (F f) y

-- | A computation on the right hand side runs first, and the rest of the block is negative.
instance
  ( p ~ Hom k
  , Dialogue k
  , KnownObj y
  , Merge g r
  , Negative (TyOf @k cont) y
  , Binds p d r a (TyOf @k cont) t cont
  , r' ~ TermP p d (Union g r) (Not y)
  )
  => Bind p (TermP p d g (Not (Not a) :: SYN k)) t cont r'
  where
  {-# INLINE (>>=) #-}
  m >>= k = runUp @d @g @r @a @y m (bound @p @d @r @a @(TyOf @k cont) k)

-- | The statement of a @rec@ block, whose continuation is its 'return'.
instance
  (Bind p (TermP p d g a) t cont r', r ~ Ret tt r')
  => Bind p (TermP p d g (a :: SYN k)) t (Ret tt cont) r
  where
  {-# INLINE (>>=) #-}
  x >>= k = Ret (x >>= \p -> unRet (k p))

-- | The body of a @rec@ block, tagged with the tuple of its variables. GHC's translation passes
-- that tuple to both 'return' and 'mfix', and this tag is what makes them the same.
type Ret :: Type -> Type -> Type
newtype Ret t x = Ret x

unRet :: Ret t x -> x
unRet (Ret x) = x

-- | The variables of a @rec@ block, as GHC tuples them up.
type RecVars :: Type -> Type -> Constraint
-- The empty tuple contains no term from which to recover the category.
class RecVars k t where
  type Vars k t :: Ctx k
  recVars :: t

instance RecVars k () where
  type Vars k () = '[]
  {-# INLINE recVars #-}
  recVars = ()

instance (Promonad p, KnownObj (a :: SYN k)) => RecVars k (TermP (p :: CAT k) d '[ '(n, a)] a) where
  {-# INLINE recVars #-}
  type Vars k (TermP p d '[ '(n, a)] a) = '[ '(n, a)]
  recVars = var @n @a

instance (RecVars k x, RecVars k y) => RecVars k (x, y) where
  {-# INLINE recVars #-}
  type Vars k (x, y) = Union (Vars k x) (Vars k y)
  recVars = (recVars @k, recVars @k)

instance (RecVars k x, RecVars k y, RecVars k z) => RecVars k (x, y, z) where
  {-# INLINE recVars #-}
  type Vars k (x, y, z) = Union (Vars k x) (Vars k (y, z))
  recVars = (recVars @k, recVars @k, recVars @k)

instance (RecVars k x, RecVars k y, RecVars k z, RecVars k w) => RecVars k (x, y, z, w) where
  {-# INLINE recVars #-}
  type Vars k (x, y, z, w) = Union (Vars k x) (Vars k (y, z, w))
  recVars = (recVars @k, recVars @k, recVars @k, recVars @k)

instance (RecVars k x, RecVars k y, RecVars k z, RecVars k w, RecVars k v) => RecVars k (x, y, z, w, v) where
  {-# INLINE recVars #-}
  type Vars k (x, y, z, w, v) = Union (Vars k x) (Vars k (y, z, w, v))
  recVars = (recVars @k, recVars @k, recVars @k, recVars @k, recVars @k)

instance
  (RecVars k x, RecVars k y, RecVars k z, RecVars k w, RecVars k v, RecVars k u)
  => RecVars k (x, y, z, w, v, u)
  where
  {-# INLINE recVars #-}
  type Vars k (x, y, z, w, v, u) = Union (Vars k x) (Vars k (y, z, w, v, u))
  recVars = (recVars @k, recVars @k, recVars @k, recVars @k, recVars @k, recVars @k)

-- | The variables of a @rec@ block again, at a depth of their own: the rest of the block uses them
-- deeper than the block did. It is asked for both ways, so that what is known about the variables
-- on either side of the block determines the other.
type AnyDepth :: Type -> Type -> Constraint
class AnyDepth t t'

instance (t' ~ ()) => AnyDepth () t'

instance (t' ~ TermP p d' g a) => AnyDepth (TermP p d g a) t'

instance (AnyDepth x x', AnyDepth y y', t' ~ (x', y')) => AnyDepth (x, y) t'

instance (AnyDepth x x', AnyDepth y y', AnyDepth z z', t' ~ (x', y', z')) => AnyDepth (x, y, z) t'

instance (AnyDepth x x', AnyDepth y y', AnyDepth z z', AnyDepth w w', t' ~ (x', y', z', w')) => AnyDepth (x, y, z, w) t'

instance
  (AnyDepth x x', AnyDepth y y', AnyDepth z z', AnyDepth w w', AnyDepth v v', t' ~ (x', y', z', w', v'))
  => AnyDepth (x, y, z, w, v) t'

instance
  ( AnyDepth x x'
  , AnyDepth y y'
  , AnyDepth z z'
  , AnyDepth w w'
  , AnyDepth v v'
  , AnyDepth u u'
  , t' ~ (x', y', z', w', v', u')
  )
  => AnyDepth (x, y, z, w, v, u) t'

-- | The end of a @rec@ block: all its variables, as the tensor of their context.
{-# INLINE return #-}
return
  :: forall k t d (p :: CAT k)
   . (Monoidal k, Promonad p, RecVars k t, KnownCtx (Vars k t))
  => t -> Ret t (TermP p d (Vars k t) (Mul (Vars k t)))
return _ = withCtxOb @(Vars k t) (Ret (MkTerm (id @p @(Interp (Mul (Vars k t))))))

-- | A @rec@ block before tracing: its body, from its context @g@ to all its variables. Which of them
-- are fed back and which are passed on is only known with the rest of the block, see the 'Bind'
-- instance.
type Rec :: forall {k}. CAT k -> Nat -> Type -> Ctx k -> Type
data Rec p d t g where
  Rec :: forall {k} (p :: CAT k) d t (g :: Ctx k). p (Interp (Mul g)) (Interp (Mul (Vars k t))) -> Rec p d t g

-- | The body of a @rec@ block, to be traced by the bind after it.
{-# INLINE mfix #-}
mfix
  :: forall {k} t d (g :: Ctx k) (p :: CAT k). (RecVars k t) => (t -> Ret t (TermP p d g (Mul (Vars k t)))) -> Rec p d t g
mfix f = case unRet (f (recVars @k @t)) of MkTerm body -> Rec body

-- The continuation stays at the same depth if GHC retains no variables of the block.
type RecDepth :: forall {k}. Nat -> Ctx k -> Nat
type family RecDepth d g where
  RecDepth d '[] = d
  RecDepth d ('(n, a) ': g) = n + 1

-- | The rest of the @do@ block after a @rec@ block, which traces it. The variables of the block
-- that the block itself uses are fed back, the ones the rest uses are passed on, a variable that
-- both use is copied, and one that neither uses is discarded. The rest gets the variables anew,
-- with the same ids, so that it can use them at its own depth.
instance
  ( Promonad p
  , MonoidalProfunctor p
  , Costrong Tensor p
  , SymMonoidal k
  , RecVars k t
  , AnyDepth t t'
  , AnyDepth t' t
  , RecVars k t'
  , cont ~ TermP p (RecDepth d (Vars k t)) (CtxOf @k cont) (TyOf @k cont)
  , r ~ TermP p d (Union (Minus (CtxOf @k cont) (Vars k t)) (Minus g (Vars k t))) (TyOf @k cont)
  , Unmerge (Inter g (Vars k t)) (Minus g (Vars k t))
  , Union (Inter g (Vars k t)) (Minus g (Vars k t)) ~ g
  , Thin (Vars k t) (Union (Inter g (Vars k t)) (Inter (CtxOf @k cont) (Vars k t)))
  , Merge (Inter g (Vars k t)) (Inter (CtxOf @k cont) (Vars k t))
  , Merge (Minus (CtxOf @k cont) (Vars k t)) (Minus g (Vars k t))
  , Unmerge (Minus (CtxOf @k cont) (Vars k t)) (Inter (CtxOf @k cont) (Vars k t))
  , Union (Minus (CtxOf @k cont) (Vars k t)) (Inter (CtxOf @k cont) (Vars k t)) ~ CtxOf @k cont
  )
  => Bind p (Rec p d t (g :: Ctx k)) t' cont r
  where
  {-# INLINE (>>=) #-}
  Rec body >>= k = case k (recVars @k @t') of
    MkTerm rest ->
      withCtxOb @(Minus (CtxOf @k cont) (Vars k t))
        ( withCtxOb @(Inter g (Vars k t))
            ( withCtxOb @(Minus g (Vars k t))
                ( withCtxOb @(Inter (CtxOf @k cont) (Vars k t))
                    ( MkTerm
                        ( rest
                            . dimap
                              (merge @(Minus (CtxOf @k cont) (Vars k t)) @(Minus g (Vars k t)))
                              (unmerge @(Minus (CtxOf @k cont) (Vars k t)) @(Inter (CtxOf @k cont) (Vars k t)))
                              ( id @p @(Interp (Mul (Minus (CtxOf @k cont) (Vars k t))))
                                  M.** coact
                                    @Tensor
                                    @p
                                    @(Interp (Mul (Inter g (Vars k t))))
                                    @(Interp (Mul (Minus g (Vars k t))))
                                    @(Interp (Mul (Inter (CtxOf @k cont) (Vars k t))))
                                    ( dimap
                                        (unmerge @(Inter g (Vars k t)) @(Minus g (Vars k t)))
                                        ( merge @(Inter g (Vars k t)) @(Inter (CtxOf @k cont) (Vars k t))
                                            . thin @(Vars k t) @(Union (Inter g (Vars k t)) (Inter (CtxOf @k cont) (Vars k t)))
                                        )
                                        body
                                    )
                              )
                        )
                    )
                )
            )
        )

-- | GHC's translation of @rec@ refers to @fail@, but pairs of variables always match.
fail :: a
fail = P.error "Proarrow.Tools.SMC.fail: a pattern did not match"