refinery-0.4.0.0: test/Spec/STLC.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Spec.STLC where
import Data.List
import Data.String (IsString(..))
import Control.Applicative
import Control.Monad.Identity
import Control.Monad.State
import Refinery.ProofState
import Refinery.Tactic
import Test.Hspec
-- Just a very simple version of Simply Typed Lambda Calculus,
-- augmented with 'Hole' so that we can have
-- incomplete extracts.
data Term
= Var String
| Hole Int
| Lam String Term
| Pair Term Term
deriving (Show, Eq)
-- The type part of simply typed lambda calculus
data Type
= TVar String
| Type :-> Type
| TPair Type Type
deriving (Show, Eq)
data TacticState = TacticState { name :: Int, meta :: Int }
deriving Show
fresh :: MonadState TacticState m => m String
fresh = do
nm <- gets (show . name)
modify (\s -> s { name = name s + 1 })
pure nm
infixr 4 :->
instance IsString Type where
fromString = TVar
-- A judgement is just a context, along with a goal
data Judgement = [(String, Type)] :- Type
deriving (Show, Eq)
instance MonadExtract Int Term String TacticState Identity where
hole = do
m <- gets meta
modify $ \ts -> ts { meta = m + 1}
pure (m, Hole m)
unsolvableHole _ = do
m <- gets meta
modify $ \ts -> ts { meta = m + 1}
pure (m, Hole m)
instance MetaSubst Int Term where
substMeta _ _ (Var s) = Var s
substMeta i t1 (Hole i') = if i == i' then t1 else (Hole i')
substMeta i t1 (Lam s body) = Lam s (substMeta i t1 body)
substMeta i t1 (Pair l r) = Pair (substMeta i t1 l) (substMeta i t1 r)
type T a = TacticT Judgement Term String TacticState Identity a
pair :: T ()
pair = rule $ \case
(hys :- TPair a b) -> Pair <$> subgoal (hys :- a) <*> subgoal (hys :- b)
_ -> unsolvable "goal mismatch: Pair"
lam :: T ()
lam = rule $ \case
(hys :- (a :-> b)) -> do
nm <- fresh
body <- subgoal $ ((nm, a) : hys) :- b
pure $ Lam nm body
_ -> unsolvable "goal mismatch: Lam"
assumption :: T ()
assumption = rule $ \ (hys :- a) ->
case find (\(_, ty) -> ty == a) hys of
Just (x, _) -> pure $ Var x
Nothing -> unsolvable "goal mismatch: Assumption"
auto :: T ()
auto = do
many_ lam
choice [ pair >> auto
, assumption
]
refine :: T ()
refine = do
many_ lam
try pair
testHandlers :: T ()
testHandlers = do
handler (\err -> pure $ err ++ " Third")
handler (\err -> pure $ err ++ " Second")
failure "First"
testHandlerAlt :: T ()
testHandlerAlt = do
handler (\err -> pure $ err ++ " Handled")
(failure "Error1") <|> (failure "Error2")
testReify :: T ()
testReify = do
lam
lam
reify pair $ \ (Proof _ _ goals) -> failure $ "Generated " <> show (length goals) <> " subgoals"
testAttempt :: T ()
testAttempt = do
lam
attempt lam (failure "Attempt Test Failed")
failure $ "Attempt Test Succeeds"
jdg :: Judgement
jdg = ([] :- ("a" :-> "b" :-> (TPair "a" "b")))
evalT :: T () -> Judgement -> Either [String] [Term]
evalT t j = fmap (fmap pf_extract) $ runIdentity $ runTacticT t j (TacticState 0 0)
stlcTests :: Spec
stlcTests = do
describe "Simply Typed Lambda Calculus" $ do
it "auto synthesize a solution" $ (evalT auto jdg) `shouldBe` (Right [(Lam "0" $ Lam "1" $ Pair (Var "0") (Var "1"))])
it "handler ordering is correct" $ (evalT testHandlers jdg) `shouldBe` (Left ["First Second Third"])
it "handler works through alt" $ (evalT testHandlerAlt jdg) `shouldBe` (Left ["Error1 Handled","Error2 Handled"])
it "reify gets the right subgoals" $ (evalT testReify jdg) `shouldBe` (Left ["Generated 2 subgoals"])
it "attempt properly handles errors" $ (evalT testAttempt jdg) `shouldBe` (Left ["Attempt Test Succeeds"])