refinery 0.3.0.0 → 0.4.0.0
raw patch · 9 files changed
+1118/−469 lines, 9 filesdep −logictPVP ok
version bump matches the API change (PVP)
Dependencies removed: logict
API changes (from Hackage documentation)
- Refinery.ProofState: accumEither :: (Semigroup a, Semigroup b) => Either a b -> Either a b -> Either a b
- Refinery.ProofState: applyCont :: Functor m => (ext -> ProofStateT ext' ext err s m a) -> ProofStateT ext' ext err s m a -> ProofStateT ext' ext err s m a
- Refinery.ProofState: axiom :: ext -> ProofStateT ext' ext err s m jdg
- Refinery.ProofState: instance (Refinery.ProofState.MonadExtract ext m, GHC.Base.Monoid w) => Refinery.ProofState.MonadExtract ext (Control.Monad.Trans.Writer.Lazy.WriterT w m)
- Refinery.ProofState: instance (Refinery.ProofState.MonadExtract ext m, GHC.Base.Monoid w) => Refinery.ProofState.MonadExtract ext (Control.Monad.Trans.Writer.Strict.WriterT w m)
- Refinery.ProofState: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Refinery.ProofState.ProofStateT ext ext err s m)
- Refinery.ProofState: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Refinery.ProofState.ProofStateT ext ext err s m)
- Refinery.ProofState: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError err (Refinery.ProofState.ProofStateT ext ext err s m)
- Refinery.ProofState: instance Refinery.ProofState.MonadExtract ext m => Refinery.ProofState.MonadExtract ext (Control.Monad.Trans.Except.ExceptT err m)
- Refinery.ProofState: instance Refinery.ProofState.MonadExtract ext m => Refinery.ProofState.MonadExtract ext (Control.Monad.Trans.Reader.ReaderT r m)
- Refinery.ProofState: instance Refinery.ProofState.MonadExtract ext m => Refinery.ProofState.MonadExtract ext (Control.Monad.Trans.State.Lazy.StateT s m)
- Refinery.ProofState: mapExtract' :: Functor m => (a -> b) -> ProofStateT ext' a err s m jdg -> ProofStateT ext' b err s m jdg
- Refinery.Tactic: class (Monad m) => MonadRule jdg ext m | m -> jdg, m -> ext
- Refinery.Tactic: gather :: MonadExtract ext m => TacticT jdg ext err s m a -> ([(a, jdg)] -> TacticT jdg ext err s m a) -> TacticT jdg ext err s m a
- Refinery.Tactic.Internal: class (Monad m) => MonadRule jdg ext m | m -> jdg, m -> ext
- Refinery.Tactic.Internal: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Refinery.Tactic.Internal.TacticT jdg ext err s m)
- Refinery.Tactic.Internal: instance Control.Monad.Reader.Class.MonadReader env m => Control.Monad.Reader.Class.MonadReader env (Refinery.Tactic.Internal.TacticT jdg ext err s m)
- Refinery.Tactic.Internal: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Refinery.Tactic.Internal.RuleT jdg ext err s m)
- Refinery.Tactic.Internal: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError err (Refinery.Tactic.Internal.RuleT jdg ext err s m)
- Refinery.Tactic.Internal: instance GHC.Base.Monad m => Control.Monad.Error.Class.MonadError err (Refinery.Tactic.Internal.TacticT jdg ext err s m)
- Refinery.Tactic.Internal: instance GHC.Base.Monad m => Refinery.Tactic.Internal.MonadRule jdg ext (Refinery.Tactic.Internal.RuleT jdg ext err s m)
- Refinery.Tactic.Internal: instance Refinery.Tactic.Internal.MonadRule jdg ext m => Refinery.Tactic.Internal.MonadRule jdg ext (Control.Monad.Trans.Except.ExceptT env m)
- Refinery.Tactic.Internal: instance Refinery.Tactic.Internal.MonadRule jdg ext m => Refinery.Tactic.Internal.MonadRule jdg ext (Control.Monad.Trans.Reader.ReaderT env m)
- Refinery.Tactic.Internal: instance Refinery.Tactic.Internal.MonadRule jdg ext m => Refinery.Tactic.Internal.MonadRule jdg ext (Control.Monad.Trans.State.Strict.StateT env m)
+ Refinery.ProofState: Handle :: ProofStateT ext' ext err s m goal -> (err -> m err) -> ProofStateT ext' ext err s m goal
+ Refinery.ProofState: PartialProof :: ext -> s -> [(meta, goal)] -> [(meta, err)] -> PartialProof s err meta goal ext
+ Refinery.ProofState: Proof :: ext -> s -> [(meta, goal)] -> Proof s meta goal ext
+ Refinery.ProofState: [pf_extract] :: Proof s meta goal ext -> ext
+ Refinery.ProofState: [pf_state] :: Proof s meta goal ext -> s
+ Refinery.ProofState: [pf_unsolvedGoals] :: Proof s meta goal ext -> [(meta, goal)]
+ Refinery.ProofState: class MetaSubst meta ext => DependentMetaSubst meta jdg ext
+ Refinery.ProofState: class MetaSubst meta ext | ext -> meta
+ Refinery.ProofState: data PartialProof s err meta goal ext
+ Refinery.ProofState: data Proof s meta goal ext
+ Refinery.ProofState: dependentSubst :: DependentMetaSubst meta jdg ext => meta -> ext -> jdg -> jdg
+ Refinery.ProofState: instance (GHC.Classes.Eq ext, GHC.Classes.Eq s, GHC.Classes.Eq meta, GHC.Classes.Eq goal) => GHC.Classes.Eq (Refinery.ProofState.Proof s meta goal ext)
+ Refinery.ProofState: instance (GHC.Classes.Eq ext, GHC.Classes.Eq s, GHC.Classes.Eq meta, GHC.Classes.Eq goal, GHC.Classes.Eq err) => GHC.Classes.Eq (Refinery.ProofState.PartialProof s err meta goal ext)
+ Refinery.ProofState: instance (GHC.Show.Show ext, GHC.Show.Show s, GHC.Show.Show meta, GHC.Show.Show goal) => GHC.Show.Show (Refinery.ProofState.Proof s meta goal ext)
+ Refinery.ProofState: instance (GHC.Show.Show ext, GHC.Show.Show s, GHC.Show.Show meta, GHC.Show.Show goal, GHC.Show.Show err) => GHC.Show.Show (Refinery.ProofState.PartialProof s err meta goal ext)
+ Refinery.ProofState: instance (Refinery.ProofState.MonadExtract meta ext err s m, GHC.Base.Monoid w) => Refinery.ProofState.MonadExtract meta ext err s (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ Refinery.ProofState: instance (Refinery.ProofState.MonadExtract meta ext err s m, GHC.Base.Monoid w) => Refinery.ProofState.MonadExtract meta ext err s (Control.Monad.Trans.Writer.Strict.WriterT w m)
+ Refinery.ProofState: instance GHC.Generics.Generic (Refinery.ProofState.PartialProof s err meta goal ext)
+ Refinery.ProofState: instance GHC.Generics.Generic (Refinery.ProofState.Proof s meta goal ext)
+ Refinery.ProofState: instance Refinery.ProofState.MonadExtract meta ext err s m => Refinery.ProofState.MonadExtract meta ext err s (Control.Monad.Trans.Except.ExceptT err m)
+ Refinery.ProofState: instance Refinery.ProofState.MonadExtract meta ext err s m => Refinery.ProofState.MonadExtract meta ext err s (Control.Monad.Trans.Reader.ReaderT r m)
+ Refinery.ProofState: instance Refinery.ProofState.MonadExtract meta ext err s m => Refinery.ProofState.MonadExtract meta ext err s (Control.Monad.Trans.State.Lazy.StateT s m)
+ Refinery.ProofState: partialProofs :: forall meta ext err s m goal. MonadExtract meta ext err s m => s -> ProofStateT ext ext err s m goal -> m (Either [PartialProof s err meta goal ext] [Proof s meta goal ext])
+ Refinery.ProofState: speculate :: forall meta ext err s m jdg x. MonadExtract meta ext err s m => s -> ProofStateT ext ext err s m jdg -> ProofStateT ext (Either err (Proof s meta jdg ext)) err s m x
+ Refinery.ProofState: substMeta :: MetaSubst meta ext => meta -> ext -> ext -> ext
+ Refinery.ProofState: unsolvableHole :: (MonadExtract meta ext err s m, MonadTrans t, MonadExtract meta ext err s m1, m ~ t m1) => err -> StateT s m (meta, ext)
+ Refinery.Tactic: PartialProof :: ext -> s -> [(meta, goal)] -> [(meta, err)] -> PartialProof s err meta goal ext
+ Refinery.Tactic: Proof :: ext -> s -> [(meta, goal)] -> Proof s meta goal ext
+ Refinery.Tactic: [pf_extract] :: Proof s meta goal ext -> ext
+ Refinery.Tactic: [pf_state] :: Proof s meta goal ext -> s
+ Refinery.Tactic: [pf_unsolvedGoals] :: Proof s meta goal ext -> [(meta, goal)]
+ Refinery.Tactic: attempt :: (MonadExtract meta ext err s m, MetaSubst meta ext) => TacticT jdg ext err s m () -> TacticT jdg ext err s m () -> TacticT jdg ext err s m ()
+ Refinery.Tactic: class MetaSubst meta ext => DependentMetaSubst meta jdg ext
+ Refinery.Tactic: class MetaSubst meta ext | ext -> meta
+ Refinery.Tactic: data PartialProof s err meta goal ext
+ Refinery.Tactic: data Proof s meta goal ext
+ Refinery.Tactic: dependentSubst :: DependentMetaSubst meta jdg ext => meta -> ext -> jdg -> jdg
+ Refinery.Tactic: evalTacticT :: MonadExtract meta ext err s m => TacticT jdg ext err s m () -> jdg -> s -> m [ext]
+ Refinery.Tactic: failure :: err -> TacticT jdg ext err s m a
+ Refinery.Tactic: handler :: (err -> m err) -> TacticT jdg ext err s m ()
+ Refinery.Tactic: handler_ :: Functor m => (err -> m ()) -> TacticT jdg ext err s m ()
+ Refinery.Tactic: inspect :: Functor m => (jdg -> a) -> TacticT jdg ext err s m a
+ Refinery.Tactic: peek :: (MetaSubst meta ext, MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> (ext -> Maybe err) -> TacticT jdg ext err s m ()
+ Refinery.Tactic: reify :: forall meta jdg ext err s m. MonadExtract meta ext err s m => TacticT jdg ext err s m () -> (Proof s meta jdg ext -> TacticT jdg ext err s m ()) -> TacticT jdg ext err s m ()
+ Refinery.Tactic: resume :: forall meta jdg ext err s m. (DependentMetaSubst meta jdg ext, Monad m) => Proof s meta jdg ext -> TacticT jdg ext err s m ()
+ Refinery.Tactic: resume' :: forall meta jdg ext err s m. (MetaSubst meta ext, Monad m) => Proof s meta jdg ext -> TacticT jdg ext err s m ()
+ Refinery.Tactic: rule_ :: Monad m => RuleT jdg ext err s m ext -> TacticT jdg ext err s m ()
+ Refinery.Tactic: runPartialTacticT :: MonadExtract meta ext err s m => TacticT jdg ext err s m () -> jdg -> s -> m (Either [PartialProof s err meta jdg ext] [Proof s meta jdg ext])
+ Refinery.Tactic: some_ :: Monad m => TacticT jdg ext err s m () -> TacticT jdg ext err s m ()
+ Refinery.Tactic: substMeta :: MetaSubst meta ext => meta -> ext -> ext -> ext
+ Refinery.Tactic: tweak :: Functor m => (ext -> ext) -> TacticT jdg ext err s m () -> TacticT jdg ext err s m ()
+ Refinery.Tactic: unsolvable :: err -> RuleT jdg ext err s m ext
+ Refinery.Tactic: unsolvableHole :: (MonadExtract meta ext err s m, MonadTrans t, MonadExtract meta ext err s m1, m ~ t m1) => err -> StateT s m (meta, ext)
+ Refinery.Tactic.Internal: instance GHC.Base.Monad m => GHC.Base.Alternative (Refinery.Tactic.Internal.RuleT jdg ext err s m)
+ Refinery.Tactic.Internal: proofState_ :: Functor m => TacticT jdg ext err s m a -> jdg -> ProofStateT ext ext err s m jdg
+ Refinery.Tactic.Internal: unsolvable :: err -> RuleT jdg ext err s m ext
- Refinery.ProofState: Failure :: err -> ProofStateT ext' ext err s m goal
+ Refinery.ProofState: Failure :: err -> (ext' -> ProofStateT ext' ext err s m goal) -> ProofStateT ext' ext err s m goal
- Refinery.ProofState: class (Monad m) => MonadExtract ext m | m -> ext
+ Refinery.ProofState: class (Monad m) => MonadExtract meta ext err s m | m -> ext, m -> err, ext -> meta
- Refinery.ProofState: hole :: (MonadExtract ext m, MonadTrans t, MonadExtract ext m1, m ~ t m1) => m ext
+ Refinery.ProofState: hole :: (MonadExtract meta ext err s m, MonadTrans t, MonadExtract meta ext err s m1, m ~ t m1) => StateT s m (meta, ext)
- Refinery.ProofState: mapExtract :: Functor m => (ext -> ext') -> (ext' -> ext) -> ProofStateT ext ext err s m jdg -> ProofStateT ext' ext' err s m jdg
+ Refinery.ProofState: mapExtract :: Functor m => (a -> ext') -> (ext -> b) -> ProofStateT ext' ext err s m jdg -> ProofStateT a b err s m jdg
- Refinery.ProofState: proofs :: forall ext err s m goal. MonadExtract ext m => s -> ProofStateT ext ext err s m goal -> m [Either err (ext, s, [goal])]
+ Refinery.ProofState: proofs :: forall ext err s m goal meta. MonadExtract meta ext err s m => s -> ProofStateT ext ext err s m goal -> m (Either [err] [Proof s meta goal ext])
- Refinery.Tactic: class (Monad m) => MonadExtract ext m | m -> ext
+ Refinery.Tactic: class (Monad m) => MonadExtract meta ext err s m | m -> ext, m -> err, ext -> meta
- Refinery.Tactic: hole :: (MonadExtract ext m, MonadTrans t, MonadExtract ext m1, m ~ t m1) => m ext
+ Refinery.Tactic: hole :: (MonadExtract meta ext err s m, MonadTrans t, MonadExtract meta ext err s m1, m ~ t m1) => StateT s m (meta, ext)
- Refinery.Tactic: pruning :: MonadExtract ext m => TacticT jdg ext err s m () -> ([jdg] -> Maybe err) -> TacticT jdg ext err s m ()
+ Refinery.Tactic: pruning :: (MetaSubst meta ext, MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> ([jdg] -> Maybe err) -> TacticT jdg ext err s m ()
- Refinery.Tactic: runTacticT :: MonadExtract ext m => TacticT jdg ext err s m () -> jdg -> s -> m [Either err (ext, s, [jdg])]
+ Refinery.Tactic: runTacticT :: MonadExtract meta ext err s m => TacticT jdg ext err s m () -> jdg -> s -> m (Either [err] [Proof s meta jdg ext])
- Refinery.Tactic: subgoal :: (MonadRule jdg ext m, MonadTrans t, MonadRule jdg ext m1, m ~ t m1) => jdg -> m ext
+ Refinery.Tactic: subgoal :: jdg -> RuleT jdg ext err s m ext
- Refinery.Tactic.Internal: subgoal :: (MonadRule jdg ext m, MonadTrans t, MonadRule jdg ext m1, m ~ t m1) => jdg -> m ext
+ Refinery.Tactic.Internal: subgoal :: jdg -> RuleT jdg ext err s m ext
Files
- ChangeLog.md +40/−5
- README.md +185/−6
- refinery.cabal +8/−6
- src/Refinery/ProofState.hs +280/−125
- src/Refinery/Tactic.hs +177/−48
- src/Refinery/Tactic/Internal.hs +34/−42
- test/Spec.hs +4/−237
- test/Spec/PropertyTests.hs +243/−0
- test/Spec/STLC.hs +147/−0
ChangeLog.md view
@@ -1,10 +1,45 @@ # Changelog for refinery --* 0.1.0.0- Initial Release of the library+* 0.4.0.0+ - How failure is handled has been refined somewhat. Previously, if a+ tactic failed, then the extraction process was terminated, and+ `proofs` would return a `Left` describing the error. This design+ worked, but led to some suboptimal error reporting. To fix this,+ the `Failure` constructor from `ProofStateT` has been changed from+ ```+ | Failure err+ ```+ to+ ```+ | Failure err (ext' -> ProofStateT ext' ext err s m goal)+ ``` -## Unreleased changes+ This allows us the option to continue the extraction process even+ in the face of failure. This option is exercised in `partialProofs`+ and `runPartialTacticT`. + - A bunch of helpful combinators have been added for extract+ manipulation inside of tactics.+ - `proofs` no longer returns a tuple, but rather a record type+ ```+ data Proof s meta goal ext = Proof { pf_extract :: ext, pf_state :: s, pf_unsolvedGoals :: [(meta, goal)] }+ ```+ - Added `handler`, and removed the `MonadError` instance for `TacticT`.+ Now, instead of recovering from errors (which was fraught with subtle issues),+ we allow the user to annotate errors instead.+ - Added some useful tactic combinators:+ - tweak+ - peek+ - poke+ - inspect+ - some_+ - Swapped the order of arguments to `mapExtract` to line up with `Profunctor`+ - Reworked `MonadExtract` to support named holes.+ - Added `reify` and `resume` combinators, which are used for inspecting the current proof state during tactic execution.+* 0.3.0.0+ - Reworked the core types of the library, which fixed a lot of the weird behavior+ that users were experiencing. * 0.2.0.0- Added Alternative/MonadPlus instances to ProofStateT, TacticT, RuleT+ - Added Alternative/MonadPlus instances to ProofStateT, TacticT, RuleT+* 0.1.0.0+ - Initial Release of the library
README.md view
@@ -1,8 +1,187 @@-# refinery+# Refinery -`refinery` is a toolkit for building proof refinement/proof automation systems, based roughly on the [-Algebraic Foundations of Proof Refinement](https://arxiv.org/abs/1703.05215).+[](https://hackage.haskell.org/package/refinery) -## Overview-The main datatype of the library is `TacticT goal extract m a`, which is a monad transformer that behaves as a tactic.-When creating your domain-specific tactics, you should use `RuleT` and `rule` to implement them.+`refinery` provides a series of language-independent building blocks for creating automated, type directed program synthesis tools.+It is currently used to power the automatic code synthesis tool [Wingman For Haskell](https://haskellwingman.dev/).++## Introduction+If you are already familiar with the idea of tactics, feel free to skip ahead to the [Usage](#Usage) section.++What exactly do we do when we sit down and write a program? Sure, we may have lofty ideas about+what it may do in the end, but I'm talking about the actual process of writing a program.+For instance, take the following (rather silly) example:+```haskell+pairing :: (a -> b) -> (a -> c) -> a -> (b,c)+pairing = _+```++The first thing we might do is look at the type, see that we are writing a function, and introduce the arguments.+```haskell+pairing :: (a -> b) -> (a -> c) -> a -> (b,c)+pairing f g a = _+```++Then, we see that we are trying to make a pair type, so we will introduce a pair constructor.+```haskell+pairing :: (a -> b) -> (a -> c) -> a -> (b,c)+pairing f g a = (_, _)+```+Then, we will see that we need to produce a `b` and a `c`, and we have two functions in scope that do that, so may as well+try them!+```haskell+pairing :: (a -> b) -> (a -> c) -> a -> (b,c)+pairing f g a = (f _, g _)+```+Now, we need an `a`, and we have one in scope, so let's use that!+```haskell+pairing :: (a -> b) -> (a -> c) -> a -> (b,c)+pairing f g a = (f a, g a)+```++Now, this entire process of writing this function was entirely mechanical. We just looked at the type of the hole,+looked at what we had in scope, and applied some simple edits to get some more holes, and repeated. This feels+like we could automate this!++Now, a "tactic" is exactly this. We can think of it morally as something like the following type: `(Type -> [Type], [Expr -> Expr])`.+In short, they break the hole down into a bunch of smaller holes, and combine expressions that fit into those holes into one big expression!+This library provides the means for creating simple tactics for _any_ language you can cook up, as well as "tactic combinators", which have+a similar flavor to parser combinators. Parser combinators let us compose small atomic parsers together to form larger ones, and Tactic+combinators let us compose together small tactics to create sophisticated tools for automatic program synthesis.++## Usage+Let's walk through the usage of this library with a small example. The full source code of this example can be found in `tests/Spec/STLC.hs`.++First, let's import the main module, along with some MTL stuff:+```+import Data.List++import Control.Monad.Identity+import Control.Monad.State++import Refinery.Tactic+```+++Now let's define a teeny tiny simply typed lambda calculus:+```haskell+-- Expressions in simply typed lambda calculus, along with holes+data Term+ = Var String+ | Hole+ | Lam String Term+ | Pair Term Term+ deriving (Show, Eq)++-- Types in our version of simply typed lambda calculus+data Type+ = TVar String+ | Type :-> Type+ | TPair Type Type+ deriving (Show, Eq)+```++Now, we are going to need to define the idea of a "type in a context", commonly referred to as a "Judgement".+```haskell+newtype Judgement = [(String, Type)] :- Type+ deriving (Show)+```++Now, a bit of boilerplate is required to tell `refinery` how to generate holes.+Most of the time, you will need to have a fresh source of variables for your holes, or you+may need to run effects when you generate them. However, in the name of simplicity, let's just use+`Identity`+```haskell+instance MonadExtract Term String Identity where+ hole = pure Hole+ unsolvableHole _ = pure Hole+```++Now for our first tactic:+```haskell+type T a = TacticT Judgement Term String Int Identity a++-- Tactic for solving holes of type (a,b)+pair :: T ()+pair = rule $ \goal ->+ case goal of+ (hys :- TPair a b) -> Pair <$> subgoal (hys :- a) <*> subgoal (hys :- b)+ _ -> unsolvable "goal mismatch: Pair"+```++Now, there is a _lot_ going on here, so let's take it apart piece by piece:+To start, let's look at `TacticT`. The first type parameter is the "goal" type.+We can think of this as the thing that we are trying to "solve". For us, this+is `Judgement`, as we are going to need to know exactly what is in scope at a given point.++The next type parameter is what the tactic is going to synthesize, commonly referred to as the+"extract".++The next three type parameters are decidedly less exciting. They represent the type of errors, the+type of the state, and the base monad. We need to have a way of generating unique names, so let's+just use `Int` as our state to accomplish this.++<!-- FIXME: Better explanation -->+That final type parameter is the type that the tactic during the course of execution. We will discuss this further in the future,+so if you are confused, feel free to ignore this type parameter for now.++Next, we call `rule` to create a "basic" tactic, that lets us inspect the current goal, and create a bunch of subgoals via `subgoal`.+As we are trying to tell `refinery` how to synthesize pairs, we case on the type of the hole. If it is a pair type, we create two+new goals, one for each component of the tuple type, and then combine the solutions to those subgoals together with a pair constructor.+If the type does not match, then we throw an error via `unsolvable`.+++Now, finally, we need a way of solving goals of the form `a -> b`.+```+lam :: T ()+lam = rule $ \case+ (hys :- (a :-> b)) -> do+ name <- gets show+ modify (+ 1)+ body <- subgoal $ ((name, a) : hys) :- b+ pure $ Lam name body+ _ -> unsolvable "goal mismatch: Lam"+```+This is where the state comes in. We look up the current state, show it for use as a name,+and then increment it so that our names are unique. We then create a subgoal for `b`,+and add our new fresh name into scope, specifying that it has type `a`.+We then get the result of the subgoal and put it in a `Lam` constructor along with our fresh name.++Finally, let's define a tactic for solving a goal by using something in scope.+```haskell+assumption :: T ()+assumption = rule $ \ (hys :- a) ->+ case find (\(_, ty) -> ty == a) hys of+ Just (x, _) -> pure $ Var x+ Nothing -> unsolvable "goal mismatch: Assumption"+```++Now, for something really exciting. Let's write a tactic that can synthesize expressions for this language. Now that we have our building blocks, this+is very easy!+```haskell+auto :: T ()+auto = do+ many_ lam+ (pair >> auto) <|> assumption+```+Before explaining how exactly this _works_, let's look at what it does!+```haskell+λ> solutions auto ([] :- (TVar "a" :-> TVar "b" :-> (TPair (TVar "a") (TVar "b")))) 0+> [Lam "0" (Lam "1" (Pair (Var "0") (Var "1")))]+```++As we can see, it generated the right thing! Let's now step through _how_ exactly it did this.+To start, `many_` is a "tactic combinator". It takes a tactic as it's first argument, and+will run it repeatedly until it fails. This will result in a single subgoal that looks something like+```haskell+[("0", TVar "a"), ("1", TVar "b")] :- (TPair (TVar "a") (TVar "b"))+```++Now, for the magic. The bind for `TacticT` will run the second tactic on _every_ subgoal created by the first.+With this crucial piece of information, we can begin to see how `auto` works. Once `many_ lam` is executed,+we execute both `pair >> auto` and `assumption` against the subgoal generated, and collect all of the+solutions found by both branches together. `pair` will generate 2 subgoals, and then `>> auto` will apply+`auto` recursively to _both_ of those subgoals.++## References+`refinery` is based roughly on [Algebraic Foundations of Proof Refinement](https://arxiv.org/abs/1703.05215)
refinery.cabal view
@@ -1,13 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.34.3. -- -- see: https://github.com/sol/hpack------ hash: c5e5c657da3ec1e29d787f8c9eb17e054b2e5ad3a9e6420df6680e4cb31ca981 name: refinery-version: 0.3.0.0+version: 0.4.0.0 synopsis: Toolkit for building proof automation systems description: Please see the README on GitHub at <https://github.com/githubuser/refinery#readme> category: Language@@ -19,6 +17,10 @@ license: BSD3 license-file: LICENSE build-type: Simple+tested-with:+ GHC ==8.6.5+ , GHC ==8.8.3+ , GHC ==8.10.4 extra-source-files: README.md ChangeLog.md@@ -40,7 +42,6 @@ build-depends: base >=4.7 && <5 , exceptions >=0.10- , logict >=0.6 , mmorph >=1 , mtl >=2 default-language: Haskell2010@@ -50,6 +51,8 @@ main-is: Spec.hs other-modules: Checkers+ Spec.PropertyTests+ Spec.STLC Paths_refinery hs-source-dirs: test@@ -60,7 +63,6 @@ , checkers , exceptions >=0.10 , hspec- , logict >=0.6 , mmorph >=1 , mtl >=2 , refinery
src/Refinery/ProofState.hs view
@@ -1,21 +1,25 @@-{-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -Wno-name-shadowing #-} -{-# LANGUAGE TupleSections #-} {-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-} -------------------------------------------------------------------------------- |+-- | The datatype that drives both Rules and Tactics+--+-- If you just want to build tactics, you probably want to use 'Refinery.Tactic' instead.+-- However, if you need to get involved in the core of the library, this is the place to start.+-- -- Module : Refinery.ProofState -- Copyright : (c) Reed Mullanix 2019 -- License : BSD-style@@ -23,7 +27,21 @@ -- -- module Refinery.ProofState-where+ ( ProofStateT(..)+ -- * Proofstate Execution+ , MonadExtract(..)+ , Proof(..)+ , PartialProof(..)+ , proofs+ , partialProofs+ , subgoals+ -- * Extract Manipulation+ , mapExtract+ -- * Speculative Execution+ , MetaSubst(..)+ , DependentMetaSubst(..)+ , speculate+ ) where import Control.Applicative import Control.Monad@@ -32,24 +50,50 @@ import qualified Control.Monad.Writer.Lazy as LW import qualified Control.Monad.Writer.Strict as SW import Control.Monad.State-import Control.Monad.Logic import Control.Monad.Morph import Control.Monad.Reader-import Data.Either+import Data.Tuple (swap) import GHC.Generics +-- | The core data type of the library.+-- This represents a reified, in-progress proof strategy for a particular goal.+--+-- NOTE: We need to split up the extract type into @ext'@ and @ext@, as+-- we want to be functorial (and monadic) in @ext@, but it shows up in both+-- co and contravariant positions. Splitting the type variable up into two solves this problem,+-- at the cost of looking ugly. data ProofStateT ext' ext err s m goal = Subgoal goal (ext' -> ProofStateT ext' ext err s m goal)+ -- ^ Represents a subgoal, and a continuation that tells+ -- us what to do with the resulting extract. | Effect (m (ProofStateT ext' ext err s m goal))+ -- ^ Run an effect. | Stateful (s -> (s, ProofStateT ext' ext err s m goal))+ -- ^ Run a stateful computation. We don't want to use 'StateT' here, as it+ -- doesn't play nice with backtracking. | Alt (ProofStateT ext' ext err s m goal) (ProofStateT ext' ext err s m goal)+ -- ^ Combine together the results of two @ProofState@s, preserving the order that they were synthesized in. | Interleave (ProofStateT ext' ext err s m goal) (ProofStateT ext' ext err s m goal)+ -- ^ Similar to 'Alt', but will interleave the results, rather than just appending them.+ -- This is useful if the first argument produces potentially infinite results. | Commit (ProofStateT ext' ext err s m goal) (ProofStateT ext' ext err s m goal)+ -- ^ 'Commit' runs the first proofstate, and only runs the second if the first+ -- does not produce any successful results. | Empty- | Failure err+ -- ^ Silent failure. Always causes a backtrack, unlike 'Failure'.+ | Failure err (ext' -> ProofStateT ext' ext err s m goal)+ -- ^ This does double duty, depending on whether or not the user calls 'proofs'+ -- or 'partialProofs'. In the first case, we ignore the continutation, backtrack, and+ -- return an error in the result of 'proofs'.+ -- In the second, we treat this as a sort of "unsolvable subgoal", and call the+ -- continuation with a hole.+ | Handle (ProofStateT ext' ext err s m goal) (err -> m err)+ -- ^ An installed error handler. These allow us to add annotations to errors,+ -- or run effects. | Axiom ext- deriving stock (Generic)+ -- ^ Represents a simple extract.+ deriving stock (Generic, Functor) instance (Show goal, Show err, Show ext, Show (m (ProofStateT ext' ext err s m goal))) => Show (ProofStateT ext' ext err s m goal) where show (Subgoal goal _) = "(Subgoal " <> show goal <> " <k>)"@@ -59,20 +103,10 @@ show (Interleave p1 p2) = "(Interleave " <> show p1 <> " " <> show p2 <> ")" show (Commit p1 p2) = "(Commit " <> show p1 <> " " <> show p2 <> ")" show Empty = "Empty"- show (Failure err) = "(Failure " <> show err <> ")"+ show (Failure err _) = "(Failure " <> show err <> " <k>)"+ show (Handle p _) = "(Handle " <> show p <> " <h>)" show (Axiom ext) = "(Axiom " <> show ext <> ")" -instance Functor m => Functor (ProofStateT ext' ext err s m) where- fmap f (Subgoal goal k) = Subgoal (f goal) (fmap f . k)- fmap f (Effect m) = Effect (fmap (fmap f) m)- fmap f (Stateful s) = Stateful $ fmap (fmap f) . s- fmap f (Alt p1 p2) = Alt (fmap f p1) (fmap f p2)- fmap f (Interleave p1 p2) = Interleave (fmap f p1) (fmap f p2)- fmap f (Commit p1 p2) = Commit (fmap f p1) (fmap f p2)- fmap _ Empty = Empty- fmap _ (Failure err) = Failure err- fmap _ (Axiom ext) = Axiom ext- instance Functor m => Applicative (ProofStateT ext ext err s m) where pure = return (<*>) = ap@@ -84,10 +118,13 @@ hoist nat (Alt p1 p2) = Alt (hoist nat p1) (hoist nat p2) hoist nat (Interleave p1 p2) = Interleave (hoist nat p1) (hoist nat p2) hoist nat (Commit p1 p2) = Commit (hoist nat p1) (hoist nat p2)- hoist _ (Failure err) = Failure err+ hoist nat (Failure err k) = Failure err $ fmap (hoist nat) k+ hoist nat (Handle p h) = Handle (hoist nat p) (nat . h) hoist _ Empty = Empty hoist _ (Axiom ext) = Axiom ext +-- | Apply a continuation that creates new proof states from an extract+-- onto all of the 'Axiom' constructors of a 'ProofStateT'. applyCont :: (Functor m) => (ext -> ProofStateT ext' ext err s m a)@@ -100,20 +137,22 @@ applyCont k (Interleave p1 p2) = Interleave (applyCont k p1) (applyCont k p2) applyCont k (Commit p1 p2) = Commit (applyCont k p1) (applyCont k p2) applyCont _ Empty = Empty-applyCont _ (Failure err) = (Failure err)+applyCont k (Failure err k') = Failure err (applyCont k . k')+applyCont k (Handle p h) = Handle (applyCont k p) h applyCont k (Axiom ext) = k ext instance Functor m => Monad (ProofStateT ext ext err s m) where return goal = Subgoal goal Axiom- (Subgoal a k) >>= f = applyCont ((>>= f) . k) (f a)- (Effect m) >>= f = Effect (fmap (>>= f) m)- (Stateful s) >>= f = Stateful $ fmap (>>= f) . s- (Alt p1 p2) >>= f = Alt (p1 >>= f) (p2 >>= f)- (Interleave p1 p2) >>= f = Interleave (p1 >>= f) (p2 >>= f)- (Commit p1 p2) >>= f = Commit (p1 >>= f) (p2 >>= f)- (Failure err) >>= _ = Failure err- Empty >>= _ = Empty- (Axiom ext) >>= _ = Axiom ext+ (Subgoal a k) >>= f = applyCont (f <=< k) (f a)+ (Effect m) >>= f = Effect (fmap (>>= f) m)+ (Stateful s) >>= f = Stateful $ fmap (>>= f) . s+ (Alt p1 p2) >>= f = Alt (p1 >>= f) (p2 >>= f)+ (Interleave p1 p2) >>= f = Interleave (p1 >>= f) (p2 >>= f)+ (Commit p1 p2) >>= f = Commit (p1 >>= f) (p2 >>= f)+ (Failure err k) >>= f = Failure err (f <=< k)+ (Handle p h) >>= f = Handle (p >>= f) h+ Empty >>= _ = Empty+ (Axiom ext) >>= _ = Axiom ext instance MonadTrans (ProofStateT ext ext err s) where lift m = Effect (fmap pure m)@@ -126,93 +165,165 @@ mzero = empty mplus = (<|>) -class (Monad m) => MonadExtract ext m | m -> ext where- -- | Generates a "hole" of type @ext@, which should represent+-- | 'MonadExtract' describes the effects required to generate named holes.+-- The @meta@ type parameter here is a so called "metavariable", which can be thought of as+-- a name for a hole.+class (Monad m) => MonadExtract meta ext err s m | m -> ext, m -> err, ext -> meta where+ -- | Generates a named "hole" of type @ext@, which should represent -- an incomplete extract.- hole :: m ext- default hole :: (MonadTrans t, MonadExtract ext m1, m ~ t m1) => m ext- hole = lift hole+ hole :: StateT s m (meta, ext)+ default hole :: (MonadTrans t, MonadExtract meta ext err s m1, m ~ t m1) => StateT s m (meta, ext)+ hole = mapStateT lift hole -instance (MonadExtract ext m) => MonadExtract ext (ReaderT r m)-instance (MonadExtract ext m) => MonadExtract ext (StateT s m)-instance (MonadExtract ext m, Monoid w) => MonadExtract ext (LW.WriterT w m)-instance (MonadExtract ext m, Monoid w) => MonadExtract ext (SW.WriterT w m)-instance (MonadExtract ext m) => MonadExtract ext (ExceptT err m)+ -- | Generates an "unsolvable hole" of type @err@, which should represent+ -- an incomplete extract that we have no hope of solving.+ --+ -- These will get generated when you generate partial extracts via 'Refinery.Tactic.runPartialTacticT'.+ unsolvableHole :: err -> StateT s m (meta, ext)+ default unsolvableHole :: (MonadTrans t, MonadExtract meta ext err s m1, m ~ t m1) => err -> StateT s m (meta, ext)+ unsolvableHole = mapStateT lift . unsolvableHole -proofs :: forall ext err s m goal. (MonadExtract ext m) => s -> ProofStateT ext ext err s m goal -> m [Either err (ext, s, [goal])]-proofs s p = go s [] p++newHole :: MonadExtract meta ext err s m => s -> m (s, (meta, ext))+newHole = fmap swap . runStateT hole++newUnsolvableHole :: MonadExtract meta ext err s m => s -> err -> m (s, (meta, ext))+newUnsolvableHole s err = fmap swap $ runStateT (unsolvableHole err) s+++instance (MonadExtract meta ext err s m) => MonadExtract meta ext err s (ReaderT r m)+instance (MonadExtract meta ext err s m) => MonadExtract meta ext err s (StateT s m)+instance (MonadExtract meta ext err s m, Monoid w) => MonadExtract meta ext err s (LW.WriterT w m)+instance (MonadExtract meta ext err s m, Monoid w) => MonadExtract meta ext err s (SW.WriterT w m)+instance (MonadExtract meta ext err s m) => MonadExtract meta ext err s (ExceptT err m)++-- | Represents a single result of the execution of some tactic.+data Proof s meta goal ext = Proof+ { pf_extract :: ext+ -- ^ The extract of the tactic.+ , pf_state :: s+ -- ^ The state at the end of tactic execution.+ , pf_unsolvedGoals :: [(meta, goal)]+ -- ^ All the goals that were still unsolved by the end of tactic execution.+ }+ deriving (Eq, Show, Generic)++-- | Interleave two lists together.+-- @+-- interleave [1,2,3,4] [5,6]+-- @+-- > [1,5,2,6,3,4]+interleave :: [a] -> [a] -> [a]+interleave (x : xs) (y : ys) = x : y : (interleave xs ys)+interleave xs [] = xs+interleave [] ys = ys++-- | Helper function for combining together two results from either 'proofs' or 'partialProofs'.+-- @prioritizing f as bs@ will use @f@ to combine together either two lists of failures or two lists of successes.+-- If we have one list of successes and one list of failures, we always take the successes.+--+-- The logic behind this is that if either 'Alt' or 'Interleave' have successes, then the failures aren't particularly interesting.+prioritizing :: (forall a. [a] -> [a] -> [a])+ -> Either [b] [c]+ -> Either [b] [c]+ -> Either [b] [c]+prioritizing combine (Left a1) (Left a2) = Left $ a1 `combine` a2+prioritizing _ (Left _) (Right b2) = Right b2+prioritizing _ (Right b1) (Left _) = Right b1+prioritizing combine (Right b1) (Right b2) = Right $ b1 `combine` b2++-- | Interpret a 'ProofStateT' into a list of (possibly incomplete) extracts.+-- This function will cause a proof to terminate when it encounters a 'Failure', and will return a 'Left'.+-- If you want to still recieve an extract even when you encounter a failure, you should use 'partialProofs'.+proofs :: forall ext err s m goal meta. (MonadExtract meta ext err s m) => s -> ProofStateT ext ext err s m goal -> m (Either [err] [(Proof s meta goal ext)])+proofs s p = go s [] pure p where- go s goals (Subgoal goal k) = do- h <- hole- (go s (goals ++ [goal]) $ k h)- go s goals (Effect m) = go s goals =<< m- go s goals (Stateful f) =+ go :: s -> [(meta, goal)] -> (err -> m err) -> ProofStateT ext ext err s m goal -> m (Either [err] [Proof s meta goal ext])+ go s goals _ (Subgoal goal k) = do+ (s', (meta, h)) <- newHole s+ -- Note [Handler Reset]:+ -- We reset the handler stack to avoid the handlers leaking across subgoals.+ -- This would happen when we had a handler that wasn't followed by an error call.+ -- pair >> goal >>= \g -> (handler_ $ \_ -> traceM $ "Handling " <> show g) <|> failure "Error"+ -- We would see the "Handling a" message when solving for b.+ (go s' (goals ++ [(meta, goal)]) pure $ k h)+ go s goals handlers (Effect m) = m >>= go s goals handlers+ go s goals handlers (Stateful f) = let (s', p) = f s- in go s' goals p- go s goals (Alt p1 p2) = liftA2 (<>) (go s goals p1) (go s goals p2)- go s goals (Interleave p1 p2) = liftA2 (interleave) (go s goals p1) (go s goals p2)- go s goals (Commit p1 p2) = go s goals p1 >>= \case- (rights -> []) -> go s goals p2- solns -> pure solns- go _ _ Empty = pure []- go _ _ (Failure err) = pure [throwError err]- go s goals (Axiom ext) = pure [Right (ext, s, goals)]+ in go s' goals handlers p+ go s goals handlers (Alt p1 p2) =+ liftA2 (prioritizing (<>)) (go s goals handlers p1) (go s goals handlers p2)+ go s goals handlers (Interleave p1 p2) =+ liftA2 (prioritizing interleave) (go s goals handlers p1) (go s goals handlers p2)+ go s goals handlers (Commit p1 p2) = go s goals handlers p1 >>= \case+ Right solns | not (null solns) -> pure $ Right solns+ solns -> (prioritizing (<>) solns) <$> go s goals handlers p2+ go _ _ _ Empty = pure $ Left []+ go _ _ handlers (Failure err _) = do+ annErr <- handlers err+ pure $ Left [annErr]+ go s goals handlers (Handle p h) =+ -- Note [Handler ordering]:+ -- If we have multiple handlers in scope, then we want the handlers closer to the error site to+ -- run /first/. This allows the handlers up the stack to add their annotations on top of the+ -- ones lower down, which is the behavior that we desire.+ -- IE: for @handler f >> handler g >> failure err@, @g@ ought to be run before @f@.+ go s goals (h >=> handlers) p+ go s goals _ (Axiom ext) = pure $ Right $ [Proof ext s goals] -accumEither :: (Semigroup a, Semigroup b) => Either a b -> Either a b -> Either a b-accumEither (Left a1) (Left a2) = Left (a1 <> a2)-accumEither (Right b1) (Right b2) = Right (b1 <> b2)-accumEither Left{} x = x-accumEither x Left{} = x+-- | The result of executing 'partialProofs'.+data PartialProof s err meta goal ext+ = PartialProof ext s [(meta, goal)] [(meta, err)]+ -- ^ A so called "partial proof". These are proofs that encountered errors+ -- during execution.+ deriving (Eq, Show, Generic) +-- | Interpret a 'ProofStateT' into a list of (possibly incomplete) extracts.+-- This function will continue producing an extract when it encounters a 'Failure', leaving+-- a hole in the extract in it's place. If you want the extraction to terminate when you encounter an error,+-- you should use 'proofs'.+partialProofs :: forall meta ext err s m goal. (MonadExtract meta ext err s m) => s -> ProofStateT ext ext err s m goal -> m (Either [PartialProof s err meta goal ext] [Proof s meta goal ext])+partialProofs s pf = go s [] [] pure pf+ where+ go :: s -> [(meta, goal)] -> [(meta, err)] -> (err -> m err) -> ProofStateT ext ext err s m goal -> m (Either [PartialProof s err meta goal ext] [Proof s meta goal ext])+ go s goals errs _ (Subgoal goal k) = do+ (s', (meta, h)) <- newHole s+ -- See Note [Handler Reset]+ go s' (goals ++ [(meta, goal)]) errs pure $ k h+ go s goals errs handlers (Effect m) = m >>= go s goals errs handlers+ go s goals errs handlers (Stateful f) =+ let (s', p) = f s+ in go s' goals errs handlers p+ go s goals errs handlers (Alt p1 p2) = liftA2 (prioritizing (<>)) (go s goals errs handlers p1) (go s goals errs handlers p2)+ go s goals errs handlers (Interleave p1 p2) = liftA2 (prioritizing interleave) (go s goals errs handlers p1) (go s goals errs handlers p2)+ go s goals errs handlers (Commit p1 p2) = go s goals errs handlers p1 >>= \case+ Right solns | not (null solns) -> pure $ Right solns+ solns -> (prioritizing (<>) solns) <$> go s goals errs handlers p2+ go _ _ _ _ Empty = pure $ Left []+ go s goals errs handlers (Failure err k) = do+ annErr <- handlers err+ (s', (meta, h)) <- newUnsolvableHole s annErr+ go s' goals (errs ++ [(meta, annErr)]) handlers $ k h+ go s goals errs handlers (Handle p h) =+ -- See Note [Handler ordering]+ go s goals errs (h >=> handlers) p+ go s goals [] _ (Axiom ext) = pure $ Right [Proof ext s goals]+ go s goals errs _ (Axiom ext) = pure $ Left [PartialProof ext s goals errs]+ instance (MonadIO m) => MonadIO (ProofStateT ext ext err s m) where liftIO = lift . liftIO instance (MonadThrow m) => MonadThrow (ProofStateT ext ext err s m) where throwM = lift . throwM -instance (MonadCatch m) => MonadCatch (ProofStateT ext ext err s m) where- catch (Subgoal goal k) handle = Subgoal goal (flip catch handle . k)- catch (Effect m) handle = Effect . catch m $ pure . handle- catch (Stateful s) handle = Stateful (fmap (flip catch handle) . s)- catch (Alt p1 p2) handle = Alt (catch p1 handle) (catch p2 handle)- catch (Interleave p1 p2) handle = Interleave (catch p1 handle) (catch p2 handle)- catch (Commit p1 p2) handle = Commit (catch p1 handle) (catch p2 handle)- catch Empty _ = Empty- catch (Failure err) _ = Failure err- catch (Axiom e) _ = (Axiom e)--instance (Monad m) => MonadError err (ProofStateT ext ext err s m) where- throwError = Failure- catchError (Subgoal goal k) handle = Subgoal goal (flip catchError handle . k)- catchError (Effect m) handle = Effect (fmap (flip catchError handle) m)- catchError (Stateful s) handle = Stateful $ fmap (flip catchError handle) . s- catchError (Alt p1 p2) handle = catchError p1 handle <|> catchError p2 handle- catchError (Interleave p1 p2) handle = Interleave (catchError p1 handle) (catchError p2 handle)- catchError (Commit p1 p2) handle = catchError p1 handle <|> catchError p2 handle- catchError Empty _ = Empty- catchError (Failure err) handle = handle err- catchError (Axiom e) _ = (Axiom e)--instance (MonadReader r m) => MonadReader r (ProofStateT ext ext err s m) where- ask = lift ask- local f (Subgoal goal k) = Subgoal goal (local f . k)- local f (Effect m) = Effect (local f m)- local f (Stateful s) = Stateful (fmap (local f) . s)- local f (Alt p1 p2) = Alt (local f p1) (local f p2)- local f (Interleave p1 p2) = Interleave (local f p1) (local f p2)- local f (Commit p1 p2) = Commit (local f p1) (local f p2)- local _ Empty = Empty- local _ (Failure err) = (Failure err)- local _ (Axiom e) = (Axiom e)- instance (Monad m) => MonadState s (ProofStateT ext ext err s m) where state f = Stateful $ \s -> let (a, s') = f s in (s', pure a) -axiom :: ext -> ProofStateT ext' ext err s m jdg-axiom = Axiom-+-- | @subgoals fs p@ will apply a list of functions producing a new 'ProofStateT' to each of the subgoals of @p@ in order.+-- If the list of functions is longer than the number of subgoals, then the extra functions are ignored.+-- If the list of functions is shorter, then we simply apply @pure@ to all of the remaining subgoals. subgoals :: (Functor m) => [jdg -> ProofStateT ext ext err s m jdg] -> ProofStateT ext ext err s m jdg -> ProofStateT ext ext err s m jdg subgoals [] (Subgoal goal k) = applyCont k (pure goal) subgoals (f:fs) (Subgoal goal k) = applyCont (subgoals fs . k) (f goal)@@ -221,30 +332,74 @@ subgoals fs (Alt p1 p2) = Alt (subgoals fs p1) (subgoals fs p2) subgoals fs (Interleave p1 p2) = Interleave (subgoals fs p1) (subgoals fs p2) subgoals fs (Commit p1 p2) = Commit (subgoals fs p1) (subgoals fs p2)-subgoals _ (Failure err) = Failure err+subgoals fs (Failure err k) = Failure err (subgoals fs . k)+subgoals fs (Handle p h) = Handle (subgoals fs p) h subgoals _ Empty = Empty subgoals _ (Axiom ext) = Axiom ext -mapExtract :: (Functor m) => (ext -> ext') -> (ext' -> ext) -> ProofStateT ext ext err s m jdg -> ProofStateT ext' ext' err s m jdg-mapExtract into out = \case- Subgoal goal k -> Subgoal goal $ mapExtract into out . k . out- Effect m -> Effect (fmap (mapExtract into out) m)- Stateful s -> Stateful (fmap (mapExtract into out) . s)- Alt t1 t2 -> Alt (mapExtract into out t1) (mapExtract into out t2)- Interleave t1 t2 -> Interleave (mapExtract into out t1) (mapExtract into out t2)- Commit t1 t2 -> Commit (mapExtract into out t1) (mapExtract into out t2)- Empty -> Empty- Failure err -> Failure err- Axiom ext -> Axiom $ into ext+-- | @mapExtract f g p@ allows yout to modify the extract type of a ProofState.+-- This witness the @Profunctor@ instance of 'ProofStateT', which we can't write without a newtype due to+-- the position of the type variables+mapExtract :: (Functor m) => (a -> ext') -> (ext -> b) -> ProofStateT ext' ext err s m jdg -> ProofStateT a b err s m jdg+mapExtract into out (Subgoal goal k) = Subgoal goal (mapExtract into out . k . into)+mapExtract into out (Effect m) = Effect (fmap (mapExtract into out) m)+mapExtract into out (Stateful s) = Stateful (fmap (mapExtract into out) . s)+mapExtract into out (Alt p1 p2) = Alt (mapExtract into out p1) (mapExtract into out p2)+mapExtract into out (Interleave p1 p2) = Interleave (mapExtract into out p1) (mapExtract into out p2)+mapExtract into out (Commit p1 p2) = Commit (mapExtract into out p1) (mapExtract into out p2)+mapExtract _ _ Empty = Empty+mapExtract into out (Failure err k) = Failure err (mapExtract into out . k . into)+mapExtract into out (Handle p h) = Handle (mapExtract into out p) h+mapExtract _ out (Axiom ext) = Axiom (out ext) -mapExtract' :: Functor m => (a -> b) -> ProofStateT ext' a err s m jdg -> ProofStateT ext' b err s m jdg-mapExtract' into = \case- Subgoal goal k -> Subgoal goal $ mapExtract' into . k- Effect m -> Effect (fmap (mapExtract' into) m)- Stateful s -> Stateful (fmap (mapExtract' into) . s)- Alt t1 t2 -> Alt (mapExtract' into t1) (mapExtract' into t2)- Interleave t1 t2 -> Interleave (mapExtract' into t1) (mapExtract' into t2)- Commit t1 t2 -> Commit (mapExtract' into t1) (mapExtract' into t2)- Empty -> Empty- Failure err -> Failure err- Axiom ext -> Axiom $ into ext+-- | 'MetaSubst' captures the notion of substituting metavariables of type @meta@ into an extract of type @ext@.+-- Note that these substitutions should most likely _not_ be capture avoiding.+class MetaSubst meta ext | ext -> meta where+ -- | @substMeta meta e1 e2@ will substitute all occurances of @meta@ in @e2@ with @e1@.+ substMeta :: meta -> ext -> ext -> ext++-- | 'DependentMetaSubst' captures the notion of substituting metavariables of type @meta@ into judgements of type @jdg@.+-- This really only matters when you are dealing with dependent types, specifically existentials.+-- For instance, consider the goal+-- Σ (x : A) (B x)+-- The type of the second goal we would produce here _depends_ on the solution to the first goal,+-- so we need to know how to substitute holes in judgements in order to properly implement 'Refinery.Tactic.resume'.+class MetaSubst meta ext => DependentMetaSubst meta jdg ext where+ -- | @dependentSubst meta e j@ will substitute all occurances of @meta@ in @j@ with @e@.+ -- This method only really makes sense if you have goals that depend on earlier extracts.+ -- If this isn't the case, don't implement this.+ dependentSubst :: meta -> ext -> jdg -> jdg++-- | @speculate s p@ will record the current state of the proof as part of the extraction process.+-- In doing so, this will also remove any subgoal constructors by filling them with holes.+speculate :: forall meta ext err s m jdg x. (MonadExtract meta ext err s m) => s -> ProofStateT ext ext err s m jdg -> ProofStateT ext (Either err (Proof s meta jdg ext)) err s m x+speculate s = go s [] pure+ where+ go :: s -> [(meta, jdg)] -> (err -> m err) -> ProofStateT ext ext err s m jdg -> ProofStateT ext (Either err (Proof s meta jdg ext)) err s m x+ go s goals _ (Subgoal goal k) = Effect $ do+ (s', (meta, h)) <- newHole s+ -- See Note [Handler Reset]+ pure $ go s' (goals ++ [(meta, goal)]) pure (k h)+ go s goals handler (Effect m) = Effect (fmap (go s goals handler) m)+ go s goals handler (Stateful st) =+ let (s', p) = st s+ in go s' goals handler p+ go s goals handler (Alt p1 p2) = Alt (go s goals handler p1) (go s goals handler p2)+ go s goals handler (Interleave p1 p2) = Interleave (go s goals handler p1) (go s goals handler p2)+ go s goals handler (Commit p1 p2) = Commit (go s goals handler p1) (go s goals handler p2)+ go _ _ _ Empty = Empty+ go _ _ handler (Failure err _) = Effect $ do+ annErr <- handler err+ pure $ Axiom $ Left annErr+ go s goals handler (Handle p h) =+ -- Note [Speculation + Handler]:+ -- When speculating, we want to _keep_ any handlers in place, as well as using them to annotate any errors.+ -- For instance, consider:+ -- reify (handler_ h >> failure "Error 1") $ \_ -> failure "Error 2"+ --+ -- We want the handler installed as a part of the reify to be executed when the failure happens+ -- in the tactic we are reifing, _and_ also when the subsequent failure happens.+ --+ -- See Note [Handler Ordering] as well for more details on the @h >=> handler@ bit.+ Handle (go s goals (h >=> handler) p) h+ go s goals _ (Axiom ext) = Axiom $ Right (Proof ext s goals)
src/Refinery/Tactic.hs view
@@ -1,16 +1,19 @@-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} -------------------------------------------------------------------------------- |+-- | Tactics and Tactic Combinators+--+-- This module contains everything you need to start defining tactics+-- and tactic combinators. -- Module : Refinery.Tactic -- Copyright : (c) Reed Mullanix 2019 -- License : BSD-style@@ -18,31 +21,53 @@ module Refinery.Tactic ( TacticT , runTacticT+ , runPartialTacticT+ , evalTacticT+ , Proof(..)+ , PartialProof(..) -- * Tactic Combinators , (<@>) , (<%>)- , commit , try+ , commit , many_+ , some_ , choice , progress- , gather- , pruning , ensure+ -- * Errors and Error Handling+ , failure+ , handler+ , handler_+ -- * Extract Manipulation+ , tweak -- * Subgoal Manipulation , goal+ , inspect , focus -- * Tactic Creation , MonadExtract(..)- , MonadRule(..) , RuleT , rule+ , rule_+ , subgoal+ , unsolvable+ -- * Introspection+ , MetaSubst(..)+ , DependentMetaSubst(..)+ , reify+ , resume+ , resume'+ , pruning+ , peek+ , attempt ) where import Control.Applicative-import Control.Monad.Except-import Control.Monad.State.Strict+import Control.Monad.State.Class +import Data.Bifunctor+ import Refinery.ProofState import Refinery.Tactic.Internal @@ -62,28 +87,71 @@ (<%>) :: TacticT jdg ext err s m a -> TacticT jdg ext err s m a -> TacticT jdg ext err s m a t1 <%> t2 = tactic $ \j -> Interleave (proofState t1 j) (proofState t2 j) --- | @commit t1 t2@ will run @t1@, and then only run @t2@ if @t1@ failed to produce any extracts.-commit :: TacticT jdg ext err s m a -> TacticT jdg ext err s m a -> TacticT jdg ext err s m a-commit t1 t2 = tactic $ \j -> Commit (proofState t1 j) (proofState t2 j)- -- | Tries to run a tactic, backtracking on failure try :: (Monad m) => TacticT jdg ext err s m () -> TacticT jdg ext err s m () try t = t <|> pure () --- | Runs a tactic repeatedly until it fails+-- | @commit t1 t2@ will run @t1@, and then run @t2@ only if @t1@ (and all subsequent tactics) failed to produce any successes.+--+-- NOTE: @commit@ does have some sneaky semantics that you have to be aware of. Specifically, it interacts a bit+-- surprisingly with '>>='. Namely, the following algebraic law holds+-- @+-- commit t1 t2 >>= f = commit (t1 >>= f) (t2 >>= f)+-- @+-- For instance, if you have something like @commit t1 t2 >>= somethingThatMayFail@, then this+-- law implies that this is the same as @commit (t1 >>= somethingThatMayFail) (t2 >>= somethingThatMayFail)@,+-- which means that we might execute @t2@ _even if_ @t1@ seemingly succeeds.+commit :: TacticT jdg ext err s m a -> TacticT jdg ext err s m a -> TacticT jdg ext err s m a+commit t1 t2 = tactic $ \j -> Commit (proofState t1 j) (proofState t2 j)++-- | Runs a tactic 0 or more times until it fails.+-- Note that 'many_' is almost always the right choice over 'many'.+-- Due to the semantics of 'Alternative', 'many' will create a bunch+-- of partially solved goals along the way, one for each iteration. many_ :: (Monad m) => TacticT jdg ext err s m () -> TacticT jdg ext err s m () many_ t = try (t >> many_ t) +-- | Runs a tactic 1 or more times until it fails.+-- Note that 'some_' is almost always the right choice over 'some'.+-- Due to the semantics of 'Alternative', 'some' will create a bunch+-- of partially solved goals along the way, one for each iteration.+some_ :: (Monad m) => TacticT jdg ext err s m () -> TacticT jdg ext err s m ()+some_ t = t >> many_ t+ -- | Get the current goal goal :: (Functor m) => TacticT jdg ext err s m jdg-goal = TacticT $ get+goal = TacticT get +-- | Inspect the current goal.+inspect :: (Functor m) => (jdg -> a) -> TacticT jdg ext err s m a+inspect f = TacticT $ gets f+ -- | @choice ts@ will run all of the tactics in the list against the current subgoals, -- and interleave their extracts in a manner similar to '<%>'. choice :: (Monad m) => [TacticT jdg ext err s m a] -> TacticT jdg ext err s m a-choice [] = empty-choice (t:ts) = t <%> choice ts+choice = foldr (<%>) empty +-- | @failure err@ will create an unsolvable hole that will be ignored by subsequent tactics.+failure :: err -> TacticT jdg ext err s m a+failure err = tactic $ \_ -> Failure err Axiom++-- | @handler f@ will install an "error handler". These let you add more context to errors, and+-- potentially run effects. For instance, you may want to take note that a particular situation is+-- unsolvable, and that we shouldn't attempt to run this series of tactics against a given goal again.+--+-- Note that handlers further down the stack get run before those higher up the stack.+-- For instance, consider the following example:+-- @+-- handler f >> handler g >> failure err+-- @+-- Here, @g@ will get run before @f@.+handler :: (err -> m err) -> TacticT jdg ext err s m ()+handler h = tactic $ \j -> Handle (Subgoal ((), j) Axiom) h++-- | A variant of 'handler' that doesn't modify the error at all, and solely exists to run effects.+handler_ :: (Functor m) => (err -> m ()) -> TacticT jdg ext err s m ()+handler_ h = handler (\err -> err <$ h err)+ -- | @progress eq err t@ applies the tactic @t@, and checks to see if the -- resulting subgoals are all equal to the initial goal by using @eq@. If they -- are, it throws @err@.@@ -92,27 +160,9 @@ j <- goal a <- t j' <- goal- if j `eq` j' then pure a else throwError err---- | @gather t f@ runs the tactic @t@, then runs @f@ with all of the generated subgoals to determine--- the next tactic to run.-gather :: (MonadExtract ext m) => TacticT jdg ext err s m a -> ([(a, jdg)] -> TacticT jdg ext err s m a) -> TacticT jdg ext err s m a-gather t f = tactic $ \j -> do- s <- get- results <- lift $ proofs s $ proofState t j- msum $ flip fmap results $ \case- Left err -> throwError err- Right (_, _, jdgs) -> proofState (f jdgs) j---- | @pruning t f@ runs the tactic @t@, and then applies a predicate to all of the generated subgoals.-pruning- :: (MonadExtract ext m)- => TacticT jdg ext err s m ()- -> ([jdg] -> Maybe err)- -> TacticT jdg ext err s m ()-pruning t p = gather t $ maybe t throwError . p . fmap snd+ if j `eq` j' then pure a else failure err --- | @filterT p f t@ runs the tactic @t@, and applies a predicate to the state after the execution of @t@. We also run+-- | @ensure p f t@ runs the tactic @t@, and applies a predicate to the state after the execution of @t@. We also run -- a "cleanup" function @f@. Note that the predicate is applied to the state _before_ the cleanup function is run. ensure :: (Monad m) => (s -> Maybe err) -> (s -> s) -> TacticT jdg ext err s m () -> TacticT jdg ext err s m () ensure p f t = check >> t@@ -125,18 +175,97 @@ s <- get modify f case p s of- Just err -> throwError err+ Just err -> unsolvable err Nothing -> pure e -- | Apply the first tactic, and then apply the second tactic focused on the @n@th subgoal. focus :: (Functor m) => TacticT jdg ext err s m () -> Int -> TacticT jdg ext err s m () -> TacticT jdg ext err s m () focus t n t' = t <@> (replicate n (pure ()) ++ [t'] ++ repeat (pure ())) +-- | @tweak f t@ lets us modify the extract produced by the tactic @t@.+tweak :: (Functor m) => (ext -> ext) -> TacticT jdg ext err s m () -> TacticT jdg ext err s m ()+tweak f t = tactic $ \j -> mapExtract id f $ proofState t j+ -- | Runs a tactic, producing a list of possible extracts, along with a list of unsolved subgoals.-runTacticT :: (MonadExtract ext m) => TacticT jdg ext err s m () -> jdg -> s -> m [Either err (ext, s, [jdg])]+-- Note that this function will backtrack on errors. If you want a version that provides partial proofs,+-- use 'runPartialTacticT'+runTacticT :: (MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> jdg -> s -> m (Either [err] [(Proof s meta jdg ext)]) runTacticT t j s = proofs s $ fmap snd $ proofState t j --- | Turn an inference rule into a tactic.+-- | Run a tactic, and get just the list of extracts, ignoring any other information.+evalTacticT :: (MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> jdg -> s -> m [ext]+evalTacticT t j s = either (const []) (map pf_extract) <$> runTacticT t j s++-- | Runs a tactic, producing a list of possible extracts, along with a list of unsolved subgoals.+-- Note that this function will produce a so called "Partial Proof". This means that we no longer backtrack on errors,+-- but rather leave an unsolved hole, and continue synthesizing a extract.+-- If you want a version that backgracks on errors, see 'runTacticT'.+--+-- Note that this version is inherently slower than 'runTacticT', as it needs to continue producing extracts.+runPartialTacticT :: (MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> jdg -> s -> m (Either [PartialProof s err meta jdg ext] [(Proof s meta jdg ext)])+runPartialTacticT t j s = partialProofs s $ fmap snd $ proofState t j++-- | Turn an inference rule that examines the current goal into a tactic. rule :: (Monad m) => (jdg -> RuleT jdg ext err s m ext) -> TacticT jdg ext err s m () rule r = tactic $ \j -> fmap ((),) $ unRuleT (r j) +-- | Turn an inference rule into a tactic.+rule_ :: (Monad m) => RuleT jdg ext err s m ext -> TacticT jdg ext err s m ()+rule_ r = tactic $ \_ -> fmap ((),) $ unRuleT r++introspect :: (MonadExtract meta ext err s m) => TacticT jdg ext err s m a -> (err -> TacticT jdg ext err s m ()) -> (Proof s meta jdg ext -> TacticT jdg ext err s m ()) -> TacticT jdg ext err s m ()+introspect t handle f = rule $ \j -> do+ s <- get+ (RuleT $ speculate s $ proofState_ t j) >>= \case+ Left err -> RuleT $ proofState_ (handle err) j+ Right pf -> RuleT $ proofState_ (f pf) j++-- | @reify t f@ will execute the tactic @t@, and resolve all of it's subgoals by filling them with holes.+-- The resulting subgoals and partial extract are then passed to @f@.+reify :: forall meta jdg ext err s m . (MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> (Proof s meta jdg ext -> TacticT jdg ext err s m ()) -> TacticT jdg ext err s m ()+reify t f = introspect t failure f++-- | @resume goals partial@ allows for resumption of execution after a call to 'reify'.+-- If your language doesn't support dependent subgoals, consider using @resume'@ instead.+resume :: forall meta jdg ext err s m. (DependentMetaSubst meta jdg ext, Monad m) => Proof s meta jdg ext -> TacticT jdg ext err s m ()+resume (Proof partialExt s goals) = rule $ \_ -> do+ put s+ solns <- dependentSubgoals goals+ pure $ foldr (\(meta, soln) ext -> substMeta meta soln ext) partialExt solns+ where+ dependentSubgoals :: [(meta, jdg)] -> RuleT jdg ext err s m [(meta, ext)]+ dependentSubgoals [] = pure []+ dependentSubgoals ((meta, g) : gs) = do+ soln <- subgoal g+ solns <- dependentSubgoals $ fmap (second (dependentSubst meta soln)) gs+ pure ((meta, soln) : solns)++-- | A version of @resume@ that doesn't perform substitution into the goal types.+-- This only makes sense if your language doesn't support dependent subgoals.+-- If it does, use @resume@ instead, as this will leave the dependent subgoals with holes in them.+resume' :: forall meta jdg ext err s m. (MetaSubst meta ext, Monad m) => Proof s meta jdg ext -> TacticT jdg ext err s m ()+resume' (Proof partialExt s goals) = rule $ \_ -> do+ put s+ solns <- traverse (\(meta, g) -> (meta, ) <$> subgoal g) goals+ pure $ foldr (\(meta, soln) ext -> substMeta meta soln ext) partialExt solns++-- | @pruning t p@ will execute @t@, and then apply @p@ to any subgoals it generates. If this predicate returns an error, we terminate the execution.+-- Otherwise, we resume execution via 'resume''.+pruning :: (MetaSubst meta ext, MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> ([jdg] -> Maybe err) -> TacticT jdg ext err s m ()+pruning t p = reify t $ \pf -> case (p $ fmap snd $ pf_unsolvedGoals pf) of+ Just err -> failure err+ Nothing -> resume' pf++-- | @peek t p@ will execute @t@, and then apply @p@ to the extract it produces. If this predicate returns an error, we terminate the execution.+-- Otherwise, we resume execution via 'resume''.+--+-- Note that the extract produced may contain holes, as it is the extract produced by running _just_ @t@ against the current goal.+peek :: (MetaSubst meta ext, MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> (ext -> Maybe err) -> TacticT jdg ext err s m ()+peek t p = reify t $ \pf -> case (p $ pf_extract pf) of+ Just err -> failure err+ Nothing -> resume' pf++-- | @attempt t1 t2@ will partially execute @t1@, inspect it's result, and only run @t2@ if it fails.+-- If @t1@ succeeded, we will 'resume'' execution of it.+attempt :: (MonadExtract meta ext err s m, MetaSubst meta ext) => TacticT jdg ext err s m () -> TacticT jdg ext err s m () -> TacticT jdg ext err s m ()+attempt t1 t2 = introspect t1 (\_ -> t2) resume'
src/Refinery/Tactic/Internal.hs view
@@ -27,9 +27,12 @@ ( TacticT(..) , tactic , proofState+ , proofState_ , mapTacticT- , MonadRule(..)+ -- * Rules , RuleT(..)+ , subgoal+ , unsolvable ) where @@ -38,7 +41,6 @@ import Control.Monad.Identity import Control.Monad.Except import Control.Monad.Catch-import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Trans () import Control.Monad.IO.Class ()@@ -56,18 +58,24 @@ -- * @err@ - The error type. We can use 'throwError' to abort the computation with a provided error -- * @s@ - The state type. -- * @m@ - The base monad.--- * @a@ - The return value. This to make @'TacticT'@ a monad, and will always be @'()'@+-- * @a@ - The return value. This to make @'TacticT'@ a monad, and will always be @'Prelude.()'@+--+-- One of the most important things about this type is it's 'Monad' instance. @t1 >> t2@+-- Will execute @t1@ against the current goal, and then execute @t2@ on _all_ of the subgoals generated+-- by @t2@.+--+-- This Monad instance is lawful, and has been tested thouroughly, and a version of it has been formally verified in Agda.+-- _However_, just because it is correct doesn't mean that it lines up with your intuitions of how Monads behave!+-- In practice, it feels like a combination of the Non-Determinisitic Monads and some of the Time Travelling ones.+-- That doesn't mean that it's impossible to understand, just that it may push the boundaries of you intuitions. newtype TacticT jdg ext err s m a = TacticT { unTacticT :: StateT jdg (ProofStateT ext ext err s m) a } deriving ( Functor , Applicative , Alternative , Monad , MonadPlus- , MonadReader env- , MonadError err , MonadIO , MonadThrow- , MonadCatch , Generic ) @@ -78,10 +86,14 @@ tactic :: (jdg -> ProofStateT ext ext err s m (a, jdg)) -> TacticT jdg ext err s m a tactic t = TacticT $ StateT t --- | Helper function for deconstructing a tactic.+-- | @proofState t j@ will deconstruct a tactic @t@ into a 'ProofStateT' by running it at @j@. proofState :: TacticT jdg ext err s m a -> jdg -> ProofStateT ext ext err s m (a, jdg) proofState t j = runStateT (unTacticT t) j +-- | Like 'proofState', but we discard the return value of @t@.+proofState_ :: (Functor m) => TacticT jdg ext err s m a -> jdg -> ProofStateT ext ext err s m jdg+proofState_ t j = execStateT (unTacticT t) j+ -- | Map the unwrapped computation using the given function mapTacticT :: (Monad m) => (m a -> m b) -> TacticT jdg ext err s m a -> TacticT jdg ext err s m b mapTacticT f (TacticT m) = TacticT $ m >>= (lift . lift . f . return)@@ -106,12 +118,16 @@ show = show . unRuleT instance Functor m => Functor (RuleT jdg ext err s m) where- fmap = coerce mapExtract'+ fmap f = coerce (mapExtract id f) instance Monad m => Applicative (RuleT jdg ext err s m) where pure = return (<*>) = ap +instance Monad m => Alternative (RuleT jdg ext err s m) where+ empty = coerce Empty+ (<|>) = coerce Alt+ instance Monad m => Monad (RuleT jdg ext err s m) where return = coerce . Axiom RuleT (Subgoal goal k) >>= f = coerce $ Subgoal goal $ fmap (bindAlaCoerce f) k@@ -119,9 +135,10 @@ RuleT (Stateful s) >>= f = coerce $ Stateful $ fmap (bindAlaCoerce f) . s RuleT (Alt p1 p2) >>= f = coerce $ Alt (bindAlaCoerce f p1) (bindAlaCoerce f p2) RuleT (Interleave p1 p2) >>= f = coerce $ Interleave (bindAlaCoerce f p1) (bindAlaCoerce f p2)- RuleT (Commit p1 p2) >>= f = coerce $ Commit (bindAlaCoerce f p1) (bindAlaCoerce f p2)+ RuleT (Commit p1 p2) >>= f = coerce $ Commit (bindAlaCoerce f p1) (bindAlaCoerce f p2) RuleT Empty >>= _ = coerce $ Empty- RuleT (Failure err) >>= _ = coerce $ Failure err+ RuleT (Failure err k) >>= f = coerce $ Failure err $ fmap (bindAlaCoerce f) k+ RuleT (Handle p h) >>= f = coerce $ Handle (bindAlaCoerce f p) h RuleT (Axiom e) >>= f = f e instance Monad m => MonadState s (RuleT jdg ext err s m) where@@ -129,28 +146,11 @@ let (a, s') = f s in (s', Axiom a) -instance MonadReader r m => MonadReader r (RuleT jdg ext err s m) where- ask = lift ask- local f (RuleT (Subgoal goal k)) = coerce $ Subgoal goal (localAlaCoerce f . k)- local f (RuleT (Effect m)) = coerce $ Effect (local f m)- local f (RuleT (Stateful s)) = coerce $ Stateful (fmap (localAlaCoerce f) . s)- local f (RuleT (Alt p1 p2)) = coerce $ Alt (localAlaCoerce f p1) (localAlaCoerce f p2)- local f (RuleT (Interleave p1 p2)) = coerce $ Interleave (localAlaCoerce f p1) (localAlaCoerce f p2)- local f (RuleT (Commit p1 p2)) = coerce $ Commit (localAlaCoerce f p1) (localAlaCoerce f p2)- local _ (RuleT Empty) = coerce $ Empty- local _ (RuleT (Failure err)) = coerce $ Failure err- local _ (RuleT (Axiom e)) = coerce $ Axiom e- bindAlaCoerce :: (Monad m, Coercible c (m b), Coercible a1 (m a2)) => (a2 -> m b) -> a1 -> c bindAlaCoerce f = coerce . (f =<<) . coerce -localAlaCoerce- :: (MonadReader r m) =>- (r -> r) -> ProofStateT ext a err s m jdg -> ProofStateT ext a err s m jdg-localAlaCoerce f = coerce . local f . RuleT- instance MonadTrans (RuleT jdg ext err s) where lift = coerce . Effect . fmap Axiom @@ -160,19 +160,11 @@ instance MonadIO m => MonadIO (RuleT jdg ext err s m) where liftIO = lift . liftIO -instance Monad m => MonadError err (RuleT jdg ext err s m) where- throwError = coerce . Failure- catchError r h = coerce $ flip catchError h $ coerce r--class (Monad m) => MonadRule jdg ext m | m -> jdg, m -> ext where- -- | Create a subgoal, and return the resulting extract.- subgoal :: jdg -> m ext- default subgoal :: (MonadTrans t, MonadRule jdg ext m1, m ~ t m1) => jdg -> m ext- subgoal = lift . subgoal--instance (Monad m) => MonadRule jdg ext (RuleT jdg ext err s m) where- subgoal j = RuleT $ Subgoal j Axiom+-- | Create a subgoal, and return the resulting extract.+subgoal :: jdg -> RuleT jdg ext err s m ext+subgoal jdg = RuleT $ Subgoal jdg Axiom -instance (MonadRule jdg ext m) => MonadRule jdg ext (ReaderT env m)-instance (MonadRule jdg ext m) => MonadRule jdg ext (StateT env m)-instance (MonadRule jdg ext m) => MonadRule jdg ext (ExceptT env m)+-- | Create an "unsolvable" hole. These holes are ignored by subsequent tactics,+-- but do not cause a backtracking failure.+unsolvable :: err -> RuleT jdg ext err s m ext+unsolvable err = RuleT $ Failure err Axiom
test/Spec.hs view
@@ -1,244 +1,11 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE UndecidableInstances #-}-{-# OPTIONS_GHC -Wredundant-constraints #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}- module Main where -import Control.Applicative-import Control.Monad-import Control.Monad.State.Strict (StateT (..))-import Control.Monad.State.Class-import Control.Monad.Error.Class-import Data.Function-import Data.Functor.Identity-import Data.Monoid (Sum (..))-import Refinery.ProofState-import Refinery.Tactic-import Refinery.Tactic.Internal import Test.Hspec-import Test.QuickCheck hiding (Failure)-import Test.QuickCheck.Checkers-import Test.QuickCheck.Classes-import Checkers -testBatch :: TestBatch -> Spec-testBatch (batchName, tests) = describe ("laws for: " ++ batchName) $- foldr (>>) (return ()) (map (uncurry it) tests)---instance (MonadExtract ext m, EqProp (m [Either err (ext, s, [a])]), Arbitrary s)- => EqProp (ProofStateT ext ext err s m a) where- (=-=) a b = property $ do- s <- arbitrary- pure $ ((=-=) `on` proofs s) a b--instance ( Show jdg- , MonadExtract ext m- , Arbitrary jdg- , EqProp (m [Either err (ext, s, [jdg])])- , Show s- , Arbitrary s- )- => EqProp (TacticT jdg ext err s m a) where- (=-=) = (=-=) `on` runTacticT . (() <$)--instance ( Show jdg- , Arbitrary jdg- , EqProp (m [Either err (ext, s, [jdg])])- , MonadExtract ext m- , Show s- , Arbitrary s- )- => EqProp (RuleT jdg ext err s m ext) where- (=-=) = (=-=) `on` rule . const--instance MonadExtract Int Identity where- hole = pure 0--instance ( CoArbitrary ext'- , Arbitrary ext- , Arbitrary err- , Arbitrary a- , Arbitrary (m (ProofStateT ext' ext err s m a))- , CoArbitrary s- , Arbitrary s- )- => Arbitrary (ProofStateT ext' ext err s m a) where- arbitrary = getSize >>= \case- n | n <= 1 -> oneof small- _ -> oneof $- [ Subgoal <$> decayArbitrary 2 <*> decayArbitrary 2- , Effect <$> arbitrary- , Alt <$> decayArbitrary 2 <*> decayArbitrary 2- , Stateful <$> arbitrary- ] ++ small- where- small =- [ pure Empty- , Failure <$> arbitrary- , Axiom <$> arbitrary- ]- shrink = genericShrink--instance (Arbitrary (m (a, s)), CoArbitrary s) => Arbitrary (StateT s m a) where- arbitrary = StateT <$> arbitrary--instance ( CoArbitrary jdg- , Arbitrary a- , Arbitrary ext- , Arbitrary err- , CoArbitrary ext- , Arbitrary jdg- , Arbitrary (m (ProofStateT ext ext err s m (a, jdg)))- , CoArbitrary s- , Arbitrary s- )- => Arbitrary (TacticT jdg ext err s m a) where- arbitrary = fmap (TacticT . StateT) arbitrary- shrink = genericShrink--instance ( Arbitrary a- , Arbitrary err- , CoArbitrary ext- , Arbitrary jdg- , Arbitrary (m (ProofStateT ext a err s m jdg))- , CoArbitrary s- , Arbitrary s- )- => Arbitrary (RuleT jdg ext err s m a) where- arbitrary = fmap RuleT arbitrary- shrink = genericShrink--decayArbitrary :: Arbitrary a => Int -> Gen a-decayArbitrary n = scale (`div` n) arbitrary--type ProofStateTest = ProofStateT Int Int String Int Identity-type RuleTest = RuleT Int Int String Int Identity-type TacticTest = TacticT (Sum Int) Int String Int Identity+import Spec.PropertyTests+import Spec.STLC main :: IO () main = hspec $ do- describe "ProofStateT" $ do- testBatch $ functor (undefined :: ProofStateTest (Int, Int, Int))- testBatch $ applicative (undefined :: ProofStateTest (Int, Int, Int))- testBatch $ alternative (undefined :: ProofStateTest Int)- testBatch $ monad (undefined :: ProofStateTest (Int, Int, Int))- testBatch $ monadPlus (undefined :: ProofStateTest (Int, Int))- testBatch $ monadState (undefined :: ProofStateTest (Int, Int))- it "distrib put over <|>" $ property $ distribPut (undefined :: ProofStateTest (Int))- describe "RuleT" $ do- testBatch $ functor (undefined :: RuleTest (Int, Int, Int))- testBatch $ applicative (undefined :: RuleTest (Int, Int, Int))- testBatch $ monad (undefined :: RuleTest (Int, Int, Int))- describe "TacticT" $ do- testBatch $ functor (undefined :: TacticTest ((), (), ()))- testBatch $ applicative (undefined :: TacticTest ((), (), ()))- testBatch $ alternative (undefined :: TacticTest ())- testBatch $ monad (undefined :: TacticTest ((), (), ()))- testBatch $ monadPlus (undefined :: TacticTest ((), ()))- testBatch $ monadState (undefined :: TacticTest ((), ()))- it "interleave - mzero" $ property $ interleaveMZero (undefined :: TacticTest Int)- it "interleave - mplus" $ property $ interleaveMPlus (undefined :: TacticTest Int)- it "distrib put over <|>" $ property $ distribPut (undefined :: TacticTest ())- it "pure absorption on commit" $ property $ absorptionPureCommit (undefined :: TacticTest Int)- it "empty identity on commit" $ property $ emptyIdentityCommit (undefined :: TacticTest Int)- it "failure identity on commit" $ property $ emptyIdentityCommit (undefined :: TacticTest Int)--leftAltBind- :: forall m a b- . (EqProp (m b), Monad m, Alternative m)- => m a -> m a -> (a -> m b)- -> Property-leftAltBind m1 m2 f =- ((m1 <|> m2) >>= f) =-= ((m1 >>= f) <|> (m2 >>= f))--rightAltBind- :: forall m a- . (EqProp (m a), Monad m, Alternative m)- => m () -> m a -> m a- -> Property-rightAltBind m1 m2 m3 =- (m1 >> (m2 <|> m3)) =-= ((m1 >> m2) <|> (m1 >> m3))--interleaveMZero- :: forall m a jdg ext err s- . (MonadExtract ext m, EqProp (m [Either err (ext, s, [jdg])]),- Show jdg, Show s, Arbitrary jdg, Arbitrary s)- => TacticT jdg ext err s m a -- ^ proxy- -> TacticT jdg ext err s m a- -> Property-interleaveMZero _ m =- (mzero <%> m) =-= m--interleaveMPlus- :: forall m a jdg ext err s- . (MonadExtract ext m, EqProp (m [Either err (ext, s, [jdg])]),- Show jdg, Show s, Arbitrary jdg, Arbitrary s)- => TacticT jdg ext err s m a -- ^ proxy- -> a- -> TacticT jdg ext err s m a- -> TacticT jdg ext err s m a- -> Property-interleaveMPlus _ a m1 m2 =- ((pure a <|> m1) <%> m2) =-= (pure a <|> (m2 <%> m1))--distribPut- :: forall s m a- . ( MonadState s m- , Alternative m- , EqProp (m a)- , Arbitrary (m a)- , Arbitrary s- , Show s- , Show (m a)- )- => m a -> Property-distribPut _ = property $ do- s <- arbitrary @s- m1 <- arbitrary @(m a)- m2 <- arbitrary @(m a)- pure $- counterexample (show s) $- counterexample (show m1) $- counterexample (show m2) $- (put s >> (m1 <|> m2)) =-= ((put s >> m1) <|> (put s >> m2))--absorptionPureCommit- :: forall m a jdg ext err s- . (MonadExtract ext m, EqProp (m [Either err (ext, s, [jdg])]),- Show jdg, Show s, Arbitrary jdg, Arbitrary s)- => TacticT jdg ext err s m a -- ^ proxy- -> a- -> TacticT jdg ext err s m a- -> Property-absorptionPureCommit _ a t =- (commit (pure a) t) =-= pure a--emptyIdentityCommit- :: forall m a jdg ext err s- . (MonadExtract ext m, EqProp (m [Either err (ext, s, [jdg])]),- Show jdg, Show s, Arbitrary jdg, Arbitrary s)- => TacticT jdg ext err s m a -- ^ proxy- -> TacticT jdg ext err s m a- -> Property-emptyIdentityCommit _ t =- (commit empty t) =-= t--failureIdentityCommit- :: forall m a jdg ext err s- . (MonadExtract ext m, EqProp (m [Either err (ext, s, [jdg])]),- Show jdg, Show s, Arbitrary jdg, Arbitrary s)- => TacticT jdg ext err s m a -- ^ proxy- -> err- -> TacticT jdg ext err s m a- -> Property-failureIdentityCommit _ e t =- (commit (throwError e) t) =-= t+ propertyTests+ stlcTests
+ test/Spec/PropertyTests.hs view
@@ -0,0 +1,243 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wredundant-constraints #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Spec.PropertyTests where++import Control.Applicative+import Control.Monad+import Control.Monad.State.Strict (StateT (..))+import Control.Monad.State.Class+import Data.Function+import Data.Functor.Identity+import Data.Monoid (Sum (..))+import Refinery.ProofState+import Refinery.Tactic+import Refinery.Tactic.Internal+import Test.Hspec+import Test.QuickCheck hiding (Failure)+import Test.QuickCheck.Checkers+import Test.QuickCheck.Classes+import Checkers+import Data.Foldable++testBatch :: TestBatch -> Spec+testBatch (batchName, tests) = describe ("laws for: " ++ batchName) $+ traverse_ (uncurry it) tests+++instance (EqProp ext, EqProp meta, EqProp s, EqProp jdg) => EqProp (Proof s meta jdg ext) where++instance (MonadExtract meta ext err s m, EqProp (m (Either [err] [Proof s meta a ext])), Arbitrary s)+ => EqProp (ProofStateT ext ext err s m a) where+ (=-=) a b = property $ do+ s <- arbitrary+ pure $ ((=-=) `on` proofs s) a b++instance ( MonadExtract meta ext err s m+ , Arbitrary jdg+ , EqProp (m (Either [err] [Proof s meta jdg ext]))+ , Show s+ , Arbitrary s+ , Show jdg+ )+ => EqProp (TacticT jdg ext err s m a) where+ (=-=) = (=-=) `on` runTacticT . (() <$)++instance ( Arbitrary jdg+ , EqProp (m (Either [err] [Proof s meta jdg ext]))+ , MonadExtract meta ext err s m+ , Arbitrary s+ , Show s , Show jdg+ )+ => EqProp (RuleT jdg ext err s m ext) where+ (=-=) = (=-=) `on` rule . const++instance MonadExtract Int Int String Int Identity where+ hole = do+ i <- get+ modify (+1)+ pure (i, 0)+ unsolvableHole _ = do+ i <- get+ modify (+1)+ pure (i, 0)++instance ( CoArbitrary ext'+ , Arbitrary ext+ , CoArbitrary err+ , Arbitrary err+ , Arbitrary a+ , Arbitrary (m (ProofStateT ext' ext err s m a))+ , Arbitrary (m err)+ , CoArbitrary s+ , Arbitrary s+ )+ => Arbitrary (ProofStateT ext' ext err s m a) where+ arbitrary = getSize >>= \case+ n | n <= 1 -> oneof small+ _ -> oneof $+ [ Subgoal <$> decayArbitrary 2 <*> decayArbitrary 2+ , Effect <$> arbitrary+ , Interleave <$> decayArbitrary 2 <*> decayArbitrary 2+ , Alt <$> decayArbitrary 2 <*> decayArbitrary 2+ , Stateful <$> arbitrary+ , Failure <$> arbitrary <*> decayArbitrary 2+ , Handle <$> decayArbitrary 2 <*> arbitrary+ ] ++ small+ where+ small =+ [ pure Empty+ , Axiom <$> arbitrary+ ]+ shrink = genericShrink++instance (Arbitrary (m (a, s)), CoArbitrary s) => Arbitrary (StateT s m a) where+ arbitrary = StateT <$> arbitrary++instance ( CoArbitrary jdg+ , Arbitrary a+ , Arbitrary ext+ , CoArbitrary err+ , Arbitrary err+ , CoArbitrary ext+ , Arbitrary jdg+ , Arbitrary (m (ProofStateT ext ext err s m (a, jdg)))+ , Arbitrary (m err)+ , CoArbitrary s+ , Arbitrary s+ )+ => Arbitrary (TacticT jdg ext err s m a) where+ arbitrary = fmap (TacticT . StateT) arbitrary+ shrink = genericShrink++instance ( Arbitrary a+ , CoArbitrary err+ , Arbitrary err+ , CoArbitrary ext+ , Arbitrary jdg+ , Arbitrary (m (ProofStateT ext a err s m jdg))+ , Arbitrary (m err)+ , CoArbitrary s+ , Arbitrary s+ )+ => Arbitrary (RuleT jdg ext err s m a) where+ arbitrary = fmap RuleT arbitrary+ shrink = genericShrink++decayArbitrary :: Arbitrary a => Int -> Gen a+decayArbitrary n = scale (`div` n) arbitrary++type ProofStateTest = ProofStateT Int Int String Int Identity+type RuleTest = RuleT Int Int String Int Identity+type TacticTest = TacticT (Sum Int) Int String Int Identity++propertyTests :: Spec+propertyTests = do+ describe "ProofStateT" $ do+ testBatch $ functor (undefined :: ProofStateTest (Int, Int, Int))+ testBatch $ applicative (undefined :: ProofStateTest (Int, Int, Int))+ testBatch $ alternative (undefined :: ProofStateTest Int)+ testBatch $ monad (undefined :: ProofStateTest (Int, Int, Int))+ testBatch $ monadPlus (undefined :: ProofStateTest (Int, Int))+ testBatch $ monadState (undefined :: ProofStateTest (Int, Int))+ it "distrib put over <|>" $ property $ distribPut (undefined :: ProofStateTest Int)+ describe "RuleT" $ do+ testBatch $ functor (undefined :: RuleTest (Int, Int, Int))+ testBatch $ applicative (undefined :: RuleTest (Int, Int, Int))+ testBatch $ alternative (undefined :: RuleTest Int)+ testBatch $ monad (undefined :: RuleTest (Int, Int, Int))+ describe "TacticT" $ do+ testBatch $ functor (undefined :: TacticTest ((), (), ()))+ testBatch $ applicative (undefined :: TacticTest ((), (), ()))+ testBatch $ alternative (undefined :: TacticTest ())+ testBatch $ monad (undefined :: TacticTest ((), (), ()))+ testBatch $ monadPlus (undefined :: TacticTest ((), ()))+ testBatch $ monadState (undefined :: TacticTest ((), ()))+ it "interleave - mzero" $ property $ interleaveMZero (undefined :: TacticTest Int)+ it "interleave - mplus" $ property $ interleaveMPlus (undefined :: TacticTest Int)+ it "distrib put over <|>" $ property $ distribPut (undefined :: TacticTest ())+ -- it "constant peek" $ property $ peekConst (undefined :: TacticTest ())++leftAltBind+ :: forall m a b+ . (EqProp (m b), Monad m, Alternative m)+ => m a -> m a -> (a -> m b)+ -> Property+leftAltBind m1 m2 f =+ ((m1 <|> m2) >>= f) =-= ((m1 >>= f) <|> (m2 >>= f))++rightAltBind+ :: forall m a+ . (EqProp (m a), Monad m, Alternative m)+ => m () -> m a -> m a+ -> Property+rightAltBind m1 m2 m3 =+ (m1 >> (m2 <|> m3)) =-= ((m1 >> m2) <|> (m1 >> m3))++interleaveMZero+ :: forall m a meta jdg ext err s+ . (MonadExtract meta ext err s m+ , EqProp (m (Either [err] [Proof s meta jdg ext]))+ , Show s , Show jdg+ , Arbitrary jdg, Arbitrary s)+ => TacticT jdg ext err s m a -- ^ proxy+ -> TacticT jdg ext err s m a+ -> Property+interleaveMZero _ m =+ (mzero <%> m) =-= m++interleaveMPlus+ :: forall m a meta jdg ext err s+ . (MonadExtract meta ext err s m+ , EqProp (m (Either [err] [Proof s meta jdg ext]))+ , Show s , Show jdg+ , Arbitrary jdg, Arbitrary s)+ => TacticT jdg ext err s m a -- ^ proxy+ -> a+ -> TacticT jdg ext err s m a+ -> TacticT jdg ext err s m a+ -> Property+interleaveMPlus _ a m1 m2 =+ ((pure a <|> m1) <%> m2) =-= (pure a <|> (m2 <%> m1))++distribPut+ :: forall s m a+ . ( MonadState s m+ , Alternative m+ , EqProp (m a)+ , Arbitrary (m a)+ , Arbitrary s+ , Show s+ , Show (m a)+ )+ => m a -> Property+distribPut _ = property $ do+ s <- arbitrary @s+ m1 <- arbitrary @(m a)+ m2 <- arbitrary @(m a)+ pure $+ counterexample (show s) $+ counterexample (show m1) $+ counterexample (show m2) $+ (put s >> (m1 <|> m2)) =-= ((put s >> m1) <|> (put s >> m2))++-- peekConst+-- :: forall m jdg ext err s+-- . (MonadExtract ext err m+-- , EqProp (m [Either err (Proof s jdg ext)])+-- , Show s , Show jdg+-- , Arbitrary jdg, Arbitrary s)+-- => TacticT jdg ext err s m () -- ^ proxy+-- -> TacticT jdg ext err s m ()+-- -> TacticT jdg ext err s m ()+-- -> Property+-- peekConst _ t t' =+-- peek t (const t') =-= (t' >> t)
+ test/Spec/STLC.hs view
@@ -0,0 +1,147 @@+{-# 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"])