packages feed

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))