proarrow-0.1.0.0: test/Examples/UntypedLambdaCalculus.hs
{-# LANGUAGE IncoherentInstances #-}
module Examples.UntypedLambdaCalculus where
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Type.Equality ((:~:) (..))
import Prelude hiding (fst, id, snd, (.))
import Test.Falsify.Generator (Gen, frequency, oneof)
import Test.Tasty (TestTree, testGroup)
import Proarrow.Category.Instance.Unit (Unit (..))
import Proarrow.Core (CAT, CategoryOf (..), Profunctor (..), Promonad (..), dimapDefault, obj, (//))
import Proarrow.Functor (Presheaf)
import Proarrow.Limit.Terminal (HasTerminalObject (..))
import Proarrow.Limit.BinaryProduct (HasBinaryProducts (..))
import Proarrow.Testing
( GenTotal (..)
, Some (..)
, Testable (..)
, TestableProfunctor
, TestableType (..)
, TestingEqShow
, mapSome
, pattern GenNonEmpty
)
import Proarrow.Testing.Laws (testBinaryProducts_, testCategory, testProfunctor, testTerminalObject)
type data CON = Z | S CON
type Sub :: CAT CON
data Sub a b where
Id :: Sub Z Z
Comp :: Sub b c -> Sub a b -> Sub a c
Cons :: Sub a b -> Tm a -> Sub a (S b)
Wk :: (Ob a) => Sub (S a) a
type Tm a = Tm' a '()
type Tm' :: Presheaf CON
data Tm' a u where
Vz :: (Ob a) => Tm' (S a) '()
Vs :: Tm a -> Tm' (S a) '()
Lam :: Tm (S a) -> Tm' a '()
App :: Tm a -> Tm a -> Tm' a '()
instance Profunctor Sub where
dimap = dimapDefault
r \\ Id = r
r \\ Comp f g = r \\ f \\ g
r \\ Cons f _ = r \\ f
r \\ Wk = r
instance Promonad Sub where
id @a = case sing @a of
SZ -> Id
SS @a' -> lift (obj @a')
Id . h = h
f . Id = f
Comp f g . h = f . (g . h)
Wk . r = pComp r
where
pComp :: (Ob b) => Sub a (S b) -> Sub a b
pComp (Cons f _) = f
pComp (Comp g h) = case pComp g of
Comp Wk x | x == g -> Comp Wk (Comp g h)
x -> x . h
pComp s = Comp Wk s
Cons f t . r = cons (f . r) (lmap r t)
instance CategoryOf CON where
type (~>) = Sub
type Ob a = (ConOb a)
instance HasTerminalObject CON where
type TerminalObject = Z
terminate @a = case sing @a of
SZ -> Id
SS @a' -> terminate @CON @a' . Wk
instance HasBinaryProducts CON where
type Z && n = n
type S a && n = S (a && n)
withObProd @a @b r =
case sing @a of
SZ -> r
SS @a' -> withObProd @CON @a' @b r
fst @a @b = case sing @a of
SZ -> terminate
SS @a' -> let f = fst @CON @a' @b in lift f
snd @a @b = case sing @a of
SZ -> id
SS @a' -> let f = snd @CON @a' @b in f . Wk \\ f
(&&&) @_ @x l r =
l // case sing @x of
SZ -> r
SS -> cons (Wk . l &&& r) (lmap l Vz)
type SingCon :: CON -> Type
data SingCon a where
SZ :: SingCon Z
SS :: (Ob a) => SingCon (S a)
class (a && Z ~ a, (a && b) && c ~ a && (b && c)) => Rules a b c
instance (a && Z ~ a, (a && b) && c ~ a && (b && c)) => Rules a b c
class (forall b c. Rules a b c) => ConOb a where sing :: SingCon a
instance ConOb Z where sing = SZ
instance (Ob a) => ConOb (S a) where sing = SS
instance Profunctor Tm' where
lmap Id t = t
lmap (Comp l r) t = lmap r (lmap l t)
lmap (Cons _ t) Vz = t
lmap (Cons f _) (Vs t) = lmap f t
lmap l (Lam t) = Lam (lmap (cons (l . Wk) Vz) t) \\ l
lmap l (App s t) = App (lmap l s) (lmap l t)
lmap Wk t = Vs t \\ t
rmap Unit = id
r \\ Vz = r
r \\ Vs t = r \\ t
r \\ Lam @a t = (case sing @(S a) of SS -> r) \\ t
r \\ App t _ = r \\ t
lift :: Sub d g -> Sub (S d) (S g)
lift s = Cons (s . Wk) Vz \\ s
($$) :: Tm a -> Tm a -> Tm a
($$) (Lam t) u = lmap (cons id u) t \\ u
($$) t u = App t u
-- simplifies @Cons (Wk . σ) (lmap σ Vz)@ to @σ@
cons :: Sub a b -> Tm a -> Sub a (S b)
cons (Comp Wk s) t = case (countWk s, countVs t) of
(Just l, Just r) | l == r -> s
_ -> Cons (Comp Wk s) t
cons s t = Cons s t
countWk :: Sub a b -> Maybe Int
countWk Wk = Just 1
countWk (Comp Wk w) = (+ 1) <$> countWk w
countWk _ = Nothing
countVs :: Tm a -> Maybe Int
countVs Vz = Just 0
countVs (Vs t) = (+ 1) <$> countVs t
countVs _ = Nothing
class b >= a where
var :: (Ob a) => SingCon a -> Tm (S b)
instance a >= a where
var _ = Vz
instance (b >= a, Ob b) => S b >= a where
var _ = lmap Wk (var @b @a sing)
lam :: (Ob a) => (SingCon a -> Tm (S a)) -> Tm a
lam f = Lam (f sing)
y :: Tm Z
y = lam \f -> let fxx = lam \x -> var f $$ (var x $$ var x) in fxx $$ fxx
test :: TestTree
test =
testGroup
"Untyped lambda calculus"
[ testCategory @CON
, testTerminalObject @CON
, testBinaryProducts_ @CON
, testGroup "Tm presheaf" [testProfunctor @Tm']
]
-- | Two contexts are the same when they have the same length. Not a method of 'Testable': the
-- laws never compare objects, and the two places below that do compare an object recovered from
-- a value (the middle context of a composite substitution, and the one a weakening drops).
eqCon :: forall (a :: CON) (b :: CON). (Ob a, Ob b) => Maybe (a :~: b)
eqCon = case (sing @a, sing @b) of
(SZ, SZ) -> Just Refl
(SS @a', SS @b') -> case eqCon @a' @b' of
Just Refl -> Just Refl
Nothing -> Nothing
_ -> Nothing
instance Testable CON where
genSome = frequency [(2, pure (Some @Z)), (1, mapSome S <$> genSome)]
showOb @a = case sing @a of
SZ -> "Z"
SS @b -> "(S " ++ showOb @CON @b ++ ")"
deriving instance Show (Sub a b)
instance Eq (Sub a b) where
Id == Id = True
Wk == Wk = True
Cons a b == Cons c d = a == c && b == d
Comp @l a b == Comp @r c d =
a // c // case eqCon @l @r of
Just Refl -> a == c && b == d
Nothing -> False
_ == _ = False
instance (Ob a, Ob b) => TestingEqShow (Sub a b)
instance (Ob a, Ob b) => TestableType (Sub a b) where
gen = case genDepthSub 8 of
Just s -> GenNonEmpty s
Nothing -> GenEmpty $ error $ "Can't generate subst of type Sub " ++ showOb @CON @a ++ " " ++ showOb @CON @b
instance TestableProfunctor Sub
deriving instance Show (Tm a)
instance Eq (Tm a) where
Vz == Vz = True
Vs l == Vs r = l == r
Lam l == Lam r = l == r
App fl xl == App fr xr = fr == fl && xl == xr
_ == _ = False
instance (Ob a, Ob b) => TestingEqShow (Tm' a b)
instance (Ob a, Ob b) => TestableType (Tm' a b) where
gen = case genDepthTm 8 of
Just t -> GenNonEmpty t
Nothing -> GenEmpty $ error $ "Can't generate term of type Tm " ++ showOb @CON @a
instance TestableProfunctor Tm'
deriving instance (Show (SingCon a))
genDepthSub :: forall a b. (Ob a, Ob b) => Int -> Maybe (Gen (Sub a b))
genDepthSub 0 = Nothing
genDepthSub d =
oneof' $
[ [pure Id | SZ <- [sing @a], SZ <- [sing @b]]
, [pure Wk | SS @a' <- [sing @a], Just Refl <- [eqCon @a' @b]]
, [liftA2 cons s t | SS <- [sing @b], Just s <- [genDepthSub (d - 1)], Just t <- [genDepthTm (d - 1)]]
]
++ [ [liftA2 (.) l r | Just l <- [genDepthSub @m @b (d - 1)], Just r <- [genDepthSub (d - 1)]]
| Some @m <- [Some @Z, Some @(S Z), Some @(S (S Z)), Some @(S (S (S Z)))]
]
genDepthTm :: forall a. (Ob a) => Int -> Maybe (Gen (Tm a))
genDepthTm 0 = Nothing
genDepthTm d =
oneof' $
[ [pure Vz | SS <- [sing @a]]
, [Lam <$> t | Just t <- [genDepthTm (d - 1)]]
, [liftA2 ($$) l r | Just l <- [genDepthTm (d - 1)], Just r <- [genDepthTm (d - 1)]]
]
++ [ [liftA2 lmap s t | Just t <- [genDepthTm @b (d - 1)], Just s <- [genDepthSub (d - 1)]]
| Some @b <- [Some @Z, Some @(S Z), Some @(S (S Z)), Some @(S (S (S Z)))]
]
oneof' :: [[Gen a]] -> Maybe (Gen a)
oneof' ls = case concat ls of
[] -> Nothing
(g : gs) -> Just (oneof (g :| gs))