packages feed

proarrow-0.1.0.0: test/Examples/Free.hs

{- HLINT ignore "Redundant $" -}
{-# LANGUAGE LinearTypes #-}

-- | A small free category on a two-object quiver, folded through an interpretation, plus a lambda
-- term built in the free cartesian closed category. Most of this is checked just by compiling it,
-- since the types are what matter, but the fold does produce a value, which is asserted at the end.
module Examples.Free where

import Data.Kind (Constraint, Type)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Falsify (testProperty)
import Prelude qualified as P

import Proarrow.Category.Enriched.Thin (Finite (..), Indexed (..))
import Proarrow.Category.Instance.Discrete (DISCRETE (..), Discrete (..))
import Proarrow.Category.Instance.Free (FREE (..), Free (..), fold)
import Proarrow.Category.Monoidal (Monoidal (..), SymMonoidal (..), UnitF, (**), type (**!))
import Proarrow.Category.Monoidal.Closed (Closed (..))
import Proarrow.Core (CategoryOf (..), Profunctor (..), Promonad (..), type (+->))
import Proarrow.Functor (FunctorForRep (..))
import Proarrow.Limit.BinaryProduct (HasBinaryProducts, (&&&), type (*!))
import Proarrow.Profunctor.Representable (Rep, Representable (..))
import Proarrow.Testing (expect)
import Unsafe.Coerce (unsafeCoerce)

type data TestTy = IntTy' | StringTy'

instance Indexed TestTy
instance Finite TestTy where type Objects TestTy = '[IntTy', StringTy']

type IntTy = D IntTy'
type StringTy = D StringTy'
data Test a b where
  Show :: Test IntTy StringTy
  Read :: Test StringTy IntTy
  Succ :: Test IntTy IntTy
  Dup :: Test StringTy StringTy

shw :: (i :: FREE cs Test) ~> EMB IntTy %1 -> i ~> EMB StringTy
shw = Emb Show

read :: (i :: FREE cs Test) ~> EMB StringTy %1 -> i ~> EMB IntTy
read = Emb Read

succ :: (i :: FREE cs Test) ~> EMB IntTy %1 -> i ~> EMB IntTy
succ = Emb Succ

dup :: (i :: FREE cs Test) ~> EMB StringTy %1 -> i ~> EMB StringTy
dup = Emb Dup

pipeline :: (i :: FREE cs Test) ~> EMB StringTy %1 -> i ~> EMB StringTy
pipeline x = dup (shw (succ (read x)))

pipelineWithInput
  :: (HasBinaryProducts (FREE cs Test)) => (i :: FREE cs Test) ~> EMB StringTy -> i ~> (EMB StringTy *! EMB StringTy)
pipelineWithInput x = x &&& pipeline x

data family Interp :: DISCRETE TestTy +-> Type
instance FunctorForRep Interp where
  type Interp @ IntTy = P.Int
  type Interp @ StringTy = P.String
  fmap Refl = id

-- | Read the string as an int, increment, show it, and duplicate, alongside the untouched input.
testFold :: P.String -> (P.String, P.String)
testFold = fold @'[HasBinaryProducts] @(Rep Interp) interp (pipelineWithInput Nil)
  where
    interp :: Test x y -> Rep Interp % x ~> Rep Interp % y
    interp Show = P.show
    interp Read = P.read
    interp Succ = P.succ
    interp Dup = \s -> s P.++ s

type SwapIn :: FC -> FC -> FC -> Constraint
class SwapIn (ia :: FC) i a | ia i -> a where
  swapIn :: (Ob a, Ob i) => a ** i ~> (ia :: FC)

instance SwapIn (a **! i) i a where
  swapIn = id
instance SwapIn (i **! a) i a where
  swapIn = swap
instance SwapIn i i UnitF where
  swapIn = leftUnitor

type Cls = '[Closed, Monoidal, SymMonoidal]
type FC = FREE Cls Test

lam
  :: forall ia i a b
   . (SwapIn ia i a, Ob i, Ob a)
  => ((i :: FC) ~> i %1 -> (ia :: FC) ~> b) %1 -> a ~> (i ~~> b)
lam = unsafeLinear \f -> curry (f id . swapIn)

($) :: forall {k} (a :: k) a' (b :: k) i. (Closed k, Ob b) => a ~> (i ~~> b) %1 -> a' ~> i %1 -> a ** a' ~> b
($) = unsafeLinear \f -> unsafeLinear \x -> apply @k @i @b . (f ** x) \\ x

testLam :: forall (a :: FC) b. (Ob a, Ob b) => UnitF ~> ((a ~~> b) ~~> (a ~~> b))
testLam = lam \f -> lam \x -> f $ x

unsafeLinear :: (a -> b) -> (a %1 -> b)
unsafeLinear = unsafeCoerce

test :: TestTree
test =
  testGroup
    "Free"
    [ testProperty
        "the pipeline folds through the interpretation"
        (expect "input paired with succ-then-duplicate" ("123", "124124") (testFold "123"))
    ]