linear-smc-2.2.2: Control/Category/FreeCartesian.hs
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE LinearTypes #-}
module Control.Category.FreeCartesian (Cat, π1, π2, embed, (▴), toSMC) where
import Prelude hiding ((.),id,curry,Ordering(..))
import Control.Category.Constrained
import Unsafe.Coerce
-- import Control.Category.InitialSMC (type TensorClosed)
data Trie k con a b where
(:▴:) :: (con b, con c) => Trie k con a b -> Trie k con a c -> Trie k con a (b,c) -- fork
Z :: Trie k con a a -- Stop
(:.) :: (con a, con b) => k a b -> Trie k con b c -> Trie k con a c -- Embed
L :: Trie k con a c -> Trie k con (a,b) c -- (∘ π₁)
R :: Trie k con b c -> Trie k con (a,b) c -- (∘ π₂)
-- composition, with the invariant that the 1st argument is a fork (so b is a product object) (unsimplified). (If it were not a fork, then the composition can be linearized)
-- Furthermore, the 2nd argument must feed both components to an 'embed', or to the final output.
-- The 1st argument isn't simplified. Because: 1. simplifying isn't going to help comparision. 2. this way we simplify only one of the copies.
(:∘) :: (con b) => Trie k con a b -> Trie k con b c -> Trie k con a c
compareTries :: Trie cat con a b -> Trie cat con a c -> Order b c
compareTries Z Z = EQ
compareTries (L f) (L g) = compareTries f g
compareTries (R f) (R g) = compareTries f g
compareTries (_ :. f) (_ :. g) = compareTries f (unsafeCoerceSource g)
-- same source: because of linearity, if the source is fully
-- consumed, there can only be a single way in which the source is
-- transformed.
compareTries (x :∘ f) (y :∘ g) = case compareTries x y of
EQ -> compareTries f g
LT -> LT
GT -> GT
compareTries ((:▴:) x _) ((:▴:) y _) = case compareTries x y of
EQ -> unsafeCoerce EQ -- the first component of the forks are equal. We maintain the invariant ① that both components are going to be used.
-- Because of linearity, this means that the 1st component fully determines the output pair.
LT -> LT
GT -> GT
compareTries Z _ = LT
compareTries _ Z = GT
compareTries (L {}) _ = LT
compareTries _ (L {})= GT
compareTries (R {}) _ = LT
compareTries _ (R {})= GT
compareTries ((:. ) {}) _ = LT
compareTries _ ((:. ) {})= GT
compareTries ((:∘) {}) _ = LT
compareTries _ ((:∘) {})= GT
pshows :: Show a => a -> ShowS
pshows x = showParen True (shows x)
instance (ProdObj con, forall x y. (con x, con y) => Show (k x y)) => Show (Trie k con a b) where
showsPrec _d = \case
f :▴: g -> showString "⟨" . shows f . showString "," . shows g . showString "⟩"
m :∘ f -> pshows m . showString "." . shows f
L k -> showString "1" . shows k
R k -> showString "2" . shows k
φ :. k -> shows φ . shows k
Z -> showString "!"
type Cat k con a b = forall c. Trie k con b c -> Trie k con a c
embed :: {-<-}(con a, con b) => {->-}k a b -> Cat k {-<-}con{->-} a b
embed φ = (φ :.)
π1 :: {-<-}con b => {->-} Cat k {-<-}con{->-} (a ⊗ b) a
π1 = L
π2 :: {-<-}con a => {->-} Cat k {-<-}con{->-} (a ⊗ b) b {-<-}
π2 = R
(▴) :: (con a, con b, con d, TensorClosed con) => (Cat k con a b) -> (Cat k con a d) -> (Cat k con a (b ⊗ d))
f ▴ g = \case Z -> h
e -> h :∘ e
where h = f Z :▴: g Z
-- -- | Make a branch with a possible continuation.
-- -- We do not create (M f Z).
-- toBranch :: (con a, con b, TensorClosed con) => FreeCartesian k con a b -> Cat k con a b
-- toBranch = \case
-- I -> id
-- (f :.: g) -> toBranch g . toBranch f
-- (Embed φ) -> (φ :.)
-- P1 -> L
-- P2 -> R
-- (f :▵: g) -> \case Z -> h
-- e -> h :∘ e
-- where h = toTrie f :▴: toTrie g
-- toTrie :: (con a, con b, TensorClosed con) => FreeCartesian k con a b -> Trie k con a b
-- toTrie f = toBranch f Z
-- | Insert a morphism (normalised)
ee :: (con a, TensorClosed con,con b, con d, Obj k ~ con, Monoidal k)
=> k a b -> Trie k con b d -> (forall c. con c => Trie k con a c -> k c d -> ξ) -> ξ
ee φ Z k = k Z φ -- A morphism at the end of computation. It's connected to the output directly, so because of linearity it will be fully consumed. So we can extract it.
ee φ f k = k (φ :. f) id
-- | Insert a fork (normalised). Precondition: f is a Fork.
trieComp :: (con a, TensorClosed con,con b, con d, Obj k ~ con, Monoidal k)
=> Trie k con a b -> Trie k con b d -> (forall c. con c => Trie k con a c -> k c d -> ξ) -> ξ
trieComp f Z k = normalize f k -- Nothing after, so we expand the fork, so that it can be merged with other branches.
trieComp f g k = k (f :∘ g) id
unsafeCoerceSource :: k a b -> k a' b
unsafeCoerceSource = unsafeCoerce
-- | Normalised fork, in the case where the heads of the components are not themselves forks.
-- Attempt a merge and return Right if successful, otherwise Left and the ordering of componets.
fork2 :: forall a b c con k ξ. (con a, con b, con c, Monoidal k, TensorClosed con, con ~ Obj k)
=> Trie k con a b -> Trie k con a c -> (forall x. con x => Trie k con a x -> k x (b⊗c) -> ξ) -> Either (Order b c) ξ
fork2 (φ :. f) (_ :. g) k = Right $ fork f (unsafeCoerceSource g) $ \fg s -> ee φ fg $ \φfg t -> k φfg (s . t)
-- same source ⇒ same morphism. -- (φ∘f ▵ φ∘g) = φ ∘ (f▵g)
fork2 (L Z) (R Z) k = Right $ k Z id -- π₁ ▵ π₂ = id
fork2 (R Z) (L Z) k = Right $ k Z swap -- π₂ ▵ π₁ = swap
fork2 (R f) (R g) k = objprod @con @a // Right $ fork f g $ \fg s -> k (R fg) s -- (π₂∘f ▵ π₂∘g) = π₂ ∘ (f▵g)
fork2 (L f) (L g) k = objprod @con @a // Right $ fork f g $ \fg s -> k (L fg) s -- shared start
fork2 (x :∘ f) (y :∘ g) k = case compareTries x y of
EQ -> Right $ fork f g $ \fg s -> trieComp x fg $ \ xfg t -> k xfg (s . t) -- shared start
LT -> Left LT -- not equal starts, return ordering
GT -> Left GT
fork2 f g _ = Left (compareTries f g) -- guaranteed not equal morphisms; we return their ordering.
-- | Normalised fork:
-- 1. Leaves are merged as much as possible.
-- 2. Fork combinators are right associated. (The first component of a fork can never itself be a fork)
-- 3. Leaves are sorted so that equal (heads) can be discovered.
-- (This performs rather badly on a long chain of forks--- but this
-- won't normally happen because we don't have very deeply nested
-- tuples, typically.)
fork :: forall a b c con k ξ. (con a, con b, con c, Monoidal k, TensorClosed con, con ~ Obj k)
=> Trie k con a b -> Trie k con a c -> (forall x. con x => Trie k con a x -> k x (b⊗c) -> ξ) -> ξ
fork (f :▴: g) h k = -- fork on left: reassociate
fork g h $ \gh s -> fork f gh $ \fgh t -> k fgh (assoc' . (id×s) . t)
fork f (g :▴: h) k = -- fork on right: attempt merging 1st and 2nd positions
case fork2 f g $ \fg s ->
-- attempt sucessful in the contiuation:
fork fg h $ \lfgh t -> k lfgh (assoc . (s × id) . t) of
Right r -> r
Left c -> case c of
EQ -> error "fork: missed equality"
LT -> k (f :▴: (g :▴: h)) id -- already in normal form
GT -> -- wrong order, swap necessary (so we're bubble-sorting the chain of forks)
fork f h $ \fh s ->
fork g fh $ \gfh t ->
k gfh (assoc . (swap × id) . assoc' . (id × s) . t)
fork f g k = case fork2 f g k of
Right r -> r -- sucessful reduction
Left c -> case c of
EQ -> error "fork: missed equality"
LT -> k (f :▴: g) id
GT -> k (g :▴: f) swap
-- | Recursively normalise. If the input is SMC equivalent in the
-- sense of the paper, the result is Z plus the SMC equivalent
-- morphism.
normalize :: forall a b con k ξ. (Obj k ~ con, Monoidal k, con a, con b, TensorClosed con)
=> Trie k con a b -> (forall c. con c => Trie k con a c -> k c b -> ξ) -> ξ
normalize t0 k = case t0 of
Z -> k Z id
f :▴: g -> normalize f $ \f' s -> normalize g $ \g' t -> (fork f' g') $ \f'g u -> k f'g ((s × t) . u)
L f -> objprod @con @a // normalize f $ \f' s -> k (L f') s
R f -> objprod @con @a // normalize f $ \f' s -> k (R f') s
f :∘ g -> normalize g $ \g' s -> trieComp f g' $ \fg' t -> k fg' (s . t)
φ :. f -> normalize f $ \f' s -> ee φ f' $ \φf' t -> k φf' (s . t)
toSMC :: forall a b con k. (Obj k ~ con, Monoidal k, con a, con b, TensorClosed con) => Cat k con a b -> k a b
toSMC t = normalize (t Z) $ \f g -> case f of
Z -> g
_ -> error "toSMC: normalisation process failed"