packages feed

bound-extras-0.0.2: examples/BiSTLC2.hs

{-# LANGUAGE DeriveFoldable         #-}
{-# LANGUAGE DeriveFunctor          #-}
{-# LANGUAGE DeriveTraversable      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs                  #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE OverloadedStrings      #-}
module BiSTLC2 (tests) where

import Bound.ScopeH
import Bound.Var            (Var (..), unvar)
import Control.Monad        (ap)
import Control.Monad.Module
import Data.Bifunctor       (first)
import Data.String          (IsString (..))
import Data.Void            (Void)
import System.FilePath      ((-<.>), (</>))
import Test.Tasty           (TestTree, testGroup)
import Test.Tasty.Golden    (goldenVsString)

import qualified Data.ByteString.Lazy.UTF8 as UTF8
import qualified Data.Text.Short           as TS

import Pretty

-------------------------------------------------------------------------------
-- Types
-------------------------------------------------------------------------------

-- | Types.
data Ty
    = Ty ShortText
    | TUnit
    | Ty :+: Ty
    | Ty :*: Ty
    | Ty :-> Ty
  deriving Eq

infixr 2 :->
infix 4 :*:
infix 3 :+:

instance IsString Ty where
    fromString = Ty . fromString

-------------------------------------------------------------------------------
-- Infession
-------------------------------------------------------------------------------

-- | Inferable terms
data Inf ty a
    -- Variable
    = V a

    -- :-> Elimination
    | App (Inf ty a) (Chk ty a)

    -- :*: Elimination-1
    | Fst (Inf ty a)

    -- :*: Elimination-2
    | Snd (Inf ty a)

    -- annotated term
    | Ann (Chk ty a) ty
  deriving (Functor, Foldable, Traversable)

(.:) :: Chk ty a -> ty -> Inf ty a
(.:) = Ann
infix 1 .:

-- | Checkable terms
data Chk ty a
    -- Converted term
    = Inf (Inf ty a)

    -- :-> Introduction
    | Lam (ScopeH () (Chk ty) (Inf ty) a)

    -- :*: Introduction
    | Pair (Chk ty a) (Chk ty a)

    -- :+: Introduction-1
    | Inl (Chk ty a)

    -- :+: Introduction-2
    | Inr (Chk ty a)

    -- :+: Elimination
    | Case (Inf ty a) (ScopeH () (Chk ty) (Inf ty) a) (ScopeH () (Chk ty) (Inf ty) a)

  deriving (Functor, Foldable, Traversable)

-------------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------------

instance IsString a => IsString (Inf ty a) where fromString = V . fromString
instance IsString a => IsString (Chk ty a) where fromString = Inf . fromString

instance Applicative (Inf ty) where
    pure = V
    (<*>) = ap

instance Monad (Inf ty) where
    return = V

    V x      >>= k = k x
    Ann x t  >>= k = Ann (x >>== k) t
    App f x  >>= k = App (f >>= k) (x >>== k)
    Fst x    >>= k = Fst (x >>= k)
    Snd x    >>= k = Snd (x >>= k)

instance ty ~ ty' => Module (Chk ty) (Inf ty') where
    Inf x         >>== k = Inf (x >>= k)
    Lam b         >>== k = Lam (b >>== k)
    Pair x y      >>== k = Pair (x >>== k) (y >>== k)
    Inl x         >>== k = Inl (x >>== k)
    Inr y         >>== k = Inr (y >>== k)
    Case e c1 c2  >>== k = Case (e >>= k) (c1 >>== k) (c2 >>== k)

instance ty ~ ty' => LiftedModule (Chk ty) (Inf ty') where
    mlift = Inf

lam_ :: Eq a => a -> Chk ty a -> Chk ty a
lam_ x b = Lam (abstract1H x b)

case_ :: Eq a => Inf ty a -> a -> Chk ty a -> a -> Chk ty a -> Chk ty a
case_ e x c1 y c2 = Case e (abstract1H x c1) (abstract1H y c2)

-------------------------------------------------------------------------------
-- Pretty
-------------------------------------------------------------------------------

instance Pretty Ty where
    ppr = return . pprTy

pprTy :: Ty -> Doc
pprTy (Ty t)    = text (TS.unpack t)
pprTy TUnit     = text "Unit"
pprTy (a :*: b) = sexpr (text "prod") [pprTy a, pprTy b]
pprTy (a :+: b) = sexpr (text "sum") [pprTy a, pprTy b]
pprTy (a :-> b) = sexpr (text "->") $ map pprTy $ a : peelArr b

instance (Pretty a, Pretty ty) => Pretty (Inf ty a) where ppr x = traverse ppr x >>= pprInf
instance (Pretty a, Pretty ty) => Pretty (Chk ty a) where ppr x = traverse ppr x >>= pprChk

pprInf :: Pretty ty => Inf ty Doc -> PrettyM Doc
pprInf (V x) = pure x
pprInf (App f x) = case peelApp f of
    (f', xs) -> sexpr
        <$> pprInf f'
        <*> traverse pprChk (xs ++ [x])
pprInf (Ann x t) = do
    x' <- pprChk x
    t' <- ppr t
    return $ sexpr (text "the") [t', x']
pprInf (Fst x)  = do
    x' <- pprInf x
    return $ sexpr (text "fst") [x']
pprInf (Snd x)  = do
    x' <- pprInf x
    return $ sexpr (text "snd") [x']

pprChk :: Pretty ty => Chk ty Doc -> PrettyM  Doc
pprChk (Inf i) = pprInf i
pprChk (Lam b) = do
    n <- text <$> fresh "x"
    b' <- pprChk (instantiate1H (V n) b)
    return $ sexpr (text "fn") [ n, b' ]
pprChk (Pair x y) = do
    x' <- pprChk x    
    y' <- pprChk y
    return $ sexpr (text "pair") [x', y']
pprChk (Inl x)  = do
    x' <- pprChk x
    return $ sexpr (text "inl") [x']
pprChk (Inr x)  = do
    x' <- pprChk x
    return $ sexpr (text "inr") [x']
pprChk (Case e c1 c2) = do
    e' <- pprInf e
    n1 <- text <$> fresh "x"
    n2 <- text <$> fresh "y"
    c1' <- pprChk (instantiate1H (V n1) c1)
    c2' <- pprChk (instantiate1H (V n2) c2)
    return $ sexpr (text "case+") [e', n1, c1', n2, c2']

-- We output
--   (0 1 2 3)
-- instead of
--   (((0 1) 2) 3)
-- small, but nice improvement!
peelApp :: Inf ty a -> (Inf ty a, [Chk ty a])
peelApp (App a b)   = (++ [b]) <$> peelApp a
peelApp e           = (e, [])

peelArr :: Ty -> [Ty]
peelArr (a :-> b) = a : peelArr b
peelArr x         = [x]

-------------------------------------------------------------------------------
-- peelApp
-------------------------------------------------------------------------------

infixl 2 $$

class SApp f g h | h -> f g where
    ($$) :: f a -> g a -> h a

instance SApp (Inf ty) (Chk ty) (Inf ty) where ($$) = App
instance SApp (Inf ty) (Chk ty) (Chk ty) where f $$ x = Inf (f $$ x)

-------------------------------------------------------------------------------
-- Normal form
-------------------------------------------------------------------------------

nfApp :: Chk ty a -> Chk ty a -> Maybe (Chk ty a)
nfApp (Inf f) x = Just $ Inf (App f x)
nfApp (Lam b) x        = chkBind (fromScopeH b) (unvar (const x) (Inf . V))
nfApp Pair {} _        = Nothing
nfApp Inl {}  _        = Nothing
nfApp Inr {}  _        = Nothing
nfApp (Case e c1 c2) x = do
    let x' = fmap F x
    c1' <- nfApp (fromScopeH c1) x'
    c2' <- nfApp (fromScopeH c2) x'
    Just $ Case e (toScopeH c1') (toScopeH c2')

nfFst :: Chk ty b -> Maybe (Chk ty b)
nfFst (Inf x)        = Just $ Inf (Fst x)
nfFst (Pair x _)     = Just x
nfFst Lam {}         = Nothing
nfFst Inl {}         = Nothing
nfFst Inr {}         = Nothing
nfFst (Case e c1 c2) = do
    c1' <- nfFst (fromScopeH c1)
    c2' <- nfFst (fromScopeH c2)
    Just $ Case e (toScopeH c1') (toScopeH c2')

nfSnd :: Chk ty b -> Maybe (Chk ty b)
nfSnd (Inf x)        = Just $ Inf (Snd x)
nfSnd (Pair x _)     = Just x
nfSnd Lam {}         = Nothing
nfSnd Inl {}         = Nothing
nfSnd Inr {}         = Nothing
nfSnd (Case e c1 c2) = do
    c1' <- nfSnd (fromScopeH c1)
    c2' <- nfSnd (fromScopeH c2)
    Just $ Case e (toScopeH c1') (toScopeH c2')

nfCase :: Chk ty a -> Chk ty (Var () a) -> Chk ty (Var () a) -> Maybe (Chk ty a)
nfCase (Inf e)        c1 c2 = Just $ Case e (toScopeH c1) (toScopeH c2)
nfCase (Inl x)        c1 _  = chkBind c1 (unvar (const x) (Inf . V))
nfCase (Inr y)        _  c2 = chkBind c2 (unvar (const y) (Inf . V))
nfCase Lam {}         _  _  = Nothing
nfCase Pair {}        _  _  = Nothing
nfCase (Case e d1 d2) c1 c2 = do
    let mkCase c = nfCase c (fmap F $ fromScopeH d1) (fmap F $ fromScopeH d2)
    c1' <- mkCase c1
    c2' <- mkCase c2
    Just $ Case e (toScopeH c1') (toScopeH c2') 

infBind :: Inf ty a -> (a -> Chk ty b) -> Maybe (Chk ty b)
infBind (Ann x _) k = chkBind x k
infBind (V x)     k = Just $ k x
infBind (App f x) k = do 
    f' <- infBind f k
    x' <- chkBind x k
    nfApp f' x'
infBind (Fst x)   k = do
    x' <- infBind x k
    nfFst x'
infBind (Snd x)   k = do
    x' <- infBind x k
    nfSnd x'

chkBind :: Chk ty a -> (a -> Chk ty b) -> Maybe (Chk ty b)
chkBind (Inf a) k = infBind a k
chkBind (Lam b) k = do
    b' <- chkBind (fromScopeH b) (unvar (Inf . V . B) (fmap F . k))
    return $ Lam $ toScopeH b'
chkBind (Pair x y) k = do
    x' <- chkBind x k
    y' <- chkBind y k
    return $ Pair x' y'
chkBind (Inl x) k = do
    x' <- chkBind x k
    return $ Inl x'
chkBind (Inr y) k = do
    y' <- chkBind y k
    return $ Inl y'
chkBind (Case e c1 c2) k = do
    e' <- infBind e k
    c1' <- chkBind (fromScopeH c1) (unvar (Inf . V . B) (fmap F . k))
    c2' <- chkBind (fromScopeH c2) (unvar (Inf . V . B) (fmap F . k))
    nfCase e' c1' c2'

-------------------------------------------------------------------------------
-- Type-checking
-------------------------------------------------------------------------------

infer :: (a -> Ty) -> Inf Ty a -> Maybe (Chk Void a, Ty)
infer f = infer' . fmap (\x -> (x, f x))

-- No error reporting :)
infer' :: Inf Ty (a, Ty) -> Maybe (Chk Void a, Ty)
infer' (V (a, at)) = Just (Inf (V a), at)
infer' (Ann x t) = do
    x' <- check' x t
    Just (x', t)
infer' (App f x) = do
    (f', ft) <- infer' f
    case ft of
        a :-> b -> do
            x' <- check' x a
            t <- nfApp f' x'
            return (t, b)
        _       -> Nothing
infer' (Fst x) = do
    (x', xt) <- infer' x
    case xt of
        (a :*: _) -> do
            t <- nfFst x'
            return (t, a)
        _ -> Nothing
infer' (Snd x) = do
    (x', xt) <- infer' x
    case xt of
        (_ :*: b) -> do
            t <- nfSnd x'
            return (t, b)
        _ -> Nothing

check' :: Chk Ty (a, Ty) -> Ty -> Maybe (Chk Void a)
check' (Lam x) t = case t of
    a :-> b -> do
        let xx = fmap (unvar (\n -> (B n, a)) (first F)) $ fromScopeH x
        xx' <- check' xx b
        return $ Lam (toScopeH xx')
    _ -> Nothing
check' (Inf x) t = do
    (x', xt) <- infer' x
    if t == xt
    then Just x'
    else Nothing
check' (Pair x y) t = case t of
    a :*: b -> do
        x' <- check' x a
        y' <- check' y b
        return (Pair x' y')
    _ -> Nothing
check' (Inl x) t = case t of
    a :+: _ -> do
        x' <- check' x a
        return (Inl x')
    _ -> Nothing
check' (Inr y) t = case t of
    _ :+: b -> do
        y' <- check' y b
        return (Inl y')
    _ -> Nothing
check' (Case e c1 c2) t = do
    (e', et) <- infer' e
    case et of
        a :+: b -> do
            let cc1 = fmap (unvar (\n -> (B n, a)) (first F)) $ fromScopeH c1
            let cc2 = fmap (unvar (\n -> (B n, b)) (first F)) $ fromScopeH c2
            cc1' <- check' cc1 t
            cc2' <- check' cc2 t
            nfCase e' cc1' cc2'
        _ -> Nothing

-------------------------------------------------------------------------------
-- Examples
-------------------------------------------------------------------------------

demo :: String -> Inf Ty ShortText -> [String]
demo name e = case infer ctx e of
    Nothing ->
        [ name ++ " = " ++ pretty e
        , "DOESN'T TYPECHECK"
        ]
    Just (nf, t) ->
        [ name ++ " : " ++ pretty t
        , name ++ " = " ++ pretty e
        , name ++ " = " ++ pretty nf
        ]
  where
    ctx "f"   = "A" :-> "B"
    ctx "a"   = "A"
    ctx "b"   = "B"
    ctx "c" = "C"
    ctx "a2c" = "A" :-> "C"
    ctx "b2c" = "B" :-> "C"
    ctx "aorb" = "A" :+: "B"
    ctx "ac2d" = "A" :-> "C" :-> "D"
    ctx "bc2d" = "B" :-> "C" :-> "D"
    ctx "aa2b" = "A" :-> "A" :-> "B"
    ctx _     = TUnit

tests :: TestTree
tests = testGroup "Bi-directional STLC 2"
    [ demo' "arr-beta"  $ (lam_ "x" ("f" $$ "x") .: "A" :-> "B") $$ "a"
    , demo' "pair-beta" $ Fst (Pair "a" "b" .: "A" :*: "B")
    , demo' "sum-beta"  $ case_ (Inl "a" .: "A" :+: "B") "x" ("a2c" $$ "x") "y" ("b2c" $$ "y") .: "C"
    , demo' "app-delta" $ (case_ "aorb" "x" ("ac2d" $$"x") "y" ("bc2d" $$ "y") .: "C" :-> "D") $$ "c"
    , demo' "redundant-case" $
        (case_ "aorb" "x" (case_ "aorb" "u" ("aa2b" $$ "x" $$ "u") "v" "v") "y" "y".: "B")
    ]
  where
    demo' name e = goldenVsString name ("examples" </> name' -<.> "txt")
        $ return $ UTF8.fromString $ unlines
        $ demo name e
      where
        name' = "stlc-2-" ++ name