mirror of
https://github.com/idris-lang/Idris2.git
synced 2024-12-22 19:21:39 +03:00
715 lines
22 KiB
Idris
715 lines
22 KiB
Idris
||| The content of this module is based on the paper
|
|
||| Auto in Agda - Programming proof search using reflection
|
|
||| by Wen Kokke and Wouter Swierstra
|
|
|
|
module Search.Auto
|
|
|
|
import Language.Reflection.TTImp
|
|
import Language.Reflection
|
|
|
|
import Data.DPair
|
|
import Data.Fin
|
|
import Data.Maybe
|
|
import Data.Nat
|
|
import Data.SnocList
|
|
import Data.String
|
|
import Data.Vect
|
|
|
|
import Syntax.PreorderReasoning
|
|
|
|
%default total
|
|
|
|
%language ElabReflection
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Basics of reflection
|
|
------------------------------------------------------------------------------
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Quoting: Id term
|
|
|
|
idTerm : TTImp
|
|
idTerm = `( (\ x => x) )
|
|
|
|
idTermTest : Auto.idTerm === let x : Name; x = UN (Basic "x") in
|
|
ILam ? MW ExplicitArg (Just x) ? (IVar ? x)
|
|
idTermTest = Refl
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Unquoting: const function
|
|
|
|
iLam : Maybe UserName -> TTImp -> TTImp -> TTImp
|
|
iLam x a sc = ILam EmptyFC MW ExplicitArg (UN <$> x) a sc
|
|
|
|
iVar : UserName -> TTImp
|
|
iVar x = IVar EmptyFC (UN x)
|
|
|
|
const : a -> b -> a
|
|
const = %runElab
|
|
(check
|
|
$ iLam (Just (Basic "v")) `(a)
|
|
$ iLam Nothing `(b)
|
|
$ iVar (Basic "v"))
|
|
|
|
constTest : Auto.const 1 2 === 1
|
|
constTest = Refl
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Section 2: Motivation
|
|
------------------------------------------------------------------------------
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Goal inspection: building tuples of default values
|
|
|
|
getPoint : Elab a
|
|
getPoint = do
|
|
let err = fail "No clue what to do"
|
|
Just g <- goal
|
|
| Nothing => err
|
|
let mval = buildValue g
|
|
maybe err check mval
|
|
|
|
where
|
|
|
|
buildValue : TTImp -> Maybe TTImp
|
|
buildValue g = do
|
|
let qNat : List ? = [`{Nat}, `{Prelude.Nat}, `{Prelude.Types.Nat}]
|
|
let qBool : List ? = [`{Bool}, `{Prelude.Bool}, `{Prelude.Basics.Bool}]
|
|
let qList : List ? = [`{List}, `{Prelude.List}, `{Prelude.Basics.List}]
|
|
let qMaybe : List ? = [`{Maybe}, `{Prelude.Maybe}, `{Prelude.Types.Maybe}]
|
|
let qPair : List ? = [`{Pair}, `{Builtin.Pair}]
|
|
case g of
|
|
IVar _ n =>
|
|
ifThenElse (n `elem` qNat) (Just `(0)) $
|
|
ifThenElse (n `elem` qBool) (Just `(False))
|
|
Nothing
|
|
IApp _ (IVar _ p) q =>
|
|
ifThenElse (p `elem` qMaybe)
|
|
(case buildValue q of
|
|
Nothing => pure `(Nothing)
|
|
Just qv => pure `(Just ~(qv)))
|
|
$ ifThenElse (p `elem` qList)
|
|
(case buildValue q of
|
|
Nothing => pure `([])
|
|
Just qv => pure `([~(qv)]))
|
|
$ Nothing
|
|
IApp _ (IApp _ (IVar _ p) q) r =>
|
|
ifThenElse (not (p `elem` qPair)) Nothing $ do
|
|
qv <- buildValue q
|
|
rv <- buildValue r
|
|
pure `(MkPair ~(qv) ~(rv))
|
|
_ => Nothing
|
|
|
|
getAPoint : (List Nat, (Bool, (Maybe (List Void, Bool, Nat))), Nat)
|
|
getAPoint = %runElab getPoint
|
|
|
|
getPointTest : Auto.getAPoint === ([0], (False, Just ([], False, 0)), 0)
|
|
getPointTest = Refl
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Proof automation: Even
|
|
|
|
data Even : Nat -> Type where
|
|
EvenZ : Even Z
|
|
EvenS : Even n -> Even (S (S n))
|
|
|
|
EvenPlus : {0 m, n : Nat} -> Even m -> Even n -> Even (m + n)
|
|
EvenPlus EvenZ en = en
|
|
EvenPlus (EvenS em) en = EvenS (EvenPlus em en)
|
|
|
|
trivial : Even n -> Even (n + 2)
|
|
trivial en = EvenPlus en (EvenS EvenZ)
|
|
|
|
||| This is called Naïve because that's not the actual implementation, it just
|
|
||| looks like this in the paper's Motivation
|
|
namespace Naïve
|
|
|
|
HintDB : Type
|
|
HintDB = SnocList Name
|
|
|
|
hints : HintDB
|
|
hints = [< `{EvenZ}, `{EvenS}, `{EvenPlus}]
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Section 3: Proof search
|
|
------------------------------------------------------------------------------
|
|
|
|
namespace RuleName
|
|
|
|
public export
|
|
data RuleName : Type where
|
|
Free : Name -> RuleName
|
|
Bound : Nat -> RuleName
|
|
|
|
export
|
|
toName : RuleName -> Name
|
|
toName (Free nm) = nm
|
|
toName (Bound n) = DN (pack $ display n)
|
|
$ MN "bound_variable_auto_search" (cast n)
|
|
|
|
where
|
|
display : Nat -> List Char
|
|
display n =
|
|
let (p, q) = divmodNatNZ n 26 SIsNonZero in
|
|
cast (q + cast 'a') :: if p == 0 then [] else display (assert_smaller n (pred p))
|
|
|
|
|
|
export
|
|
Show RuleName where
|
|
show (Free nm) = "Free \{show nm}"
|
|
show (Bound n) = "Bound \{show n}"
|
|
-- show . toName
|
|
|
|
namespace TermName
|
|
|
|
public export
|
|
data TermName : Type where
|
|
UName : Name -> TermName
|
|
Bound : Nat -> TermName
|
|
Arrow : TermName
|
|
|
|
export
|
|
Eq TermName where
|
|
UName n1 == UName n2 = n1 == n2
|
|
Bound n1 == Bound n2 = n1 == n2
|
|
Arrow == Arrow = True
|
|
_ == _ = False
|
|
|
|
||| Proof search terms
|
|
public export
|
|
data Tm : Nat -> Type where
|
|
Var : Fin n -> Tm n
|
|
Con : TermName -> List (Tm n) -> Tm n
|
|
|
|
export
|
|
showTerm : (ns : SnocList Name) -> Tm (length ns) -> String
|
|
showTerm ns = go Open where
|
|
|
|
goVar : (ns : SnocList Name) -> Fin (length ns) -> String
|
|
goVar (_ :< n) FZ = show n
|
|
goVar (ns :< _) (FS k) = goVar ns k
|
|
|
|
go : Prec -> Tm (length ns) -> String
|
|
go d (Var k) = goVar ns k
|
|
go d (Con (UName n) []) = show n -- (dropNS n)
|
|
go d (Con Arrow [a,b])
|
|
= showParens (d > Open) $
|
|
unwords [ go App a, "->", go Open b ]
|
|
go d (Con (UName n) [a,b])
|
|
= showParens (d > Open) $
|
|
-- let n = dropNS n in
|
|
if isOp n then unwords [ go App a, show n, go Open b ]
|
|
else unwords [ show n, go App a, go App b ]
|
|
go d (Con (UName n) ts)
|
|
= showParens (d > Open) $
|
|
unwords (show n :: assert_total (map (go App) ts))
|
|
go _ _ = ""
|
|
|
|
public export
|
|
Env : (Nat -> Type) -> Nat -> Nat -> Type
|
|
Env t m n = Fin m -> t n
|
|
|
|
export
|
|
rename : Env Fin m n -> Tm m -> Tm n
|
|
rename rho (Var x) = Var (rho x)
|
|
rename rho (Con f xs) = Con f (assert_total $ map (rename rho) xs)
|
|
|
|
export
|
|
subst : Env Tm m n -> Tm m -> Tm n
|
|
subst rho (Var x) = rho x
|
|
subst rho (Con f xs) = Con f (assert_total $ map (subst rho) xs)
|
|
|
|
export
|
|
split : Tm m -> (List (Tm m), Tm m)
|
|
split = go [<] where
|
|
|
|
go : SnocList (Tm m) -> Tm m -> (List (Tm m), Tm m)
|
|
go acc (Con Arrow [a,b]) = go (acc :< a) b
|
|
go acc t = (acc <>> [], t)
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Interlude: First-order unification by structural recursion by Conor McBride
|
|
------------------------------------------------------------------------------
|
|
|
|
export
|
|
thin : Fin (S n) -> Fin n -> Fin (S n)
|
|
thin FZ y = FS y
|
|
thin (FS x) FZ = FZ
|
|
thin (FS x) (FS y) = FS (thin x y)
|
|
|
|
export
|
|
thinInjective : (x : Fin (S n)) -> (y, z : Fin n) -> thin x y === thin x z -> y === z
|
|
thinInjective FZ y z eq = injective eq
|
|
thinInjective (FS x) FZ FZ eq = Refl
|
|
thinInjective (FS x) (FS y) (FS z) eq = cong FS (thinInjective x y z $ injective eq)
|
|
|
|
export
|
|
{x : Fin (S n)} -> Injective (thin x) where
|
|
injective = thinInjective x ? ?
|
|
|
|
export
|
|
thinnedApart : (x : Fin (S n)) -> (y : Fin n) -> Not (thin x y === x)
|
|
thinnedApart FZ y = absurd
|
|
thinnedApart (FS x) FZ = absurd
|
|
thinnedApart (FS x) (FS y) = thinnedApart x y . injective
|
|
|
|
export
|
|
thinApart : (x, y : Fin (S n)) -> Not (x === y) -> (y' ** thin x y' === y)
|
|
thinApart FZ FZ neq = absurd (neq Refl)
|
|
thinApart FZ (FS y') neq = (y' ** Refl)
|
|
thinApart (FS FZ) FZ neq = (FZ ** Refl)
|
|
thinApart (FS (FS x)) FZ neq = (FZ ** Refl)
|
|
thinApart (FS x@FZ) (FS y) neq = bimap FS (\prf => cong FS prf) (thinApart x y (\prf => neq $ cong FS prf))
|
|
thinApart (FS x@(FS{})) (FS y) neq = bimap FS (\prf => cong FS prf) (thinApart x y (\prf => neq $ cong FS prf))
|
|
|
|
public export
|
|
data Thicken : (x, y : Fin n) -> Type where
|
|
EQ : Thicken x x
|
|
NEQ : (y : Fin n) -> Thicken x (thin x y)
|
|
|
|
export
|
|
thicken : (x, y : Fin n) -> Thicken x y
|
|
thicken FZ FZ = EQ
|
|
thicken FZ (FS y) = NEQ y
|
|
thicken (FS FZ) FZ = NEQ FZ
|
|
thicken (FS (FS{})) FZ = NEQ FZ
|
|
thicken (FS x) (FS y) with (thicken x y)
|
|
thicken (FS x) (FS x) | EQ = EQ
|
|
thicken (FS x) (FS (thin x y)) | NEQ y = NEQ (FS y)
|
|
|
|
export
|
|
check : Fin (S n) -> Tm (S n) -> Maybe (Tm n)
|
|
check x (Var y) = case thicken x y of
|
|
EQ => Nothing
|
|
NEQ y' => Just (Var y')
|
|
check x (Con f ts) = Con f <$> assert_total (traverse (check x) ts)
|
|
|
|
|
|
export
|
|
for : Tm n -> Fin (S n) -> Env Tm (S n) n
|
|
(t `for` x) y = case thicken x y of
|
|
EQ => t
|
|
NEQ y' => Var y'
|
|
|
|
public export
|
|
data AList : (m, n : Nat) -> Type where
|
|
Lin : AList m m
|
|
(:<) : AList m n -> (Fin (S m), Tm m) -> AList (S m) n
|
|
|
|
export
|
|
(++) : AList m n -> AList l m -> AList l n
|
|
xts ++ [<] = xts
|
|
xts ++ (yus :< yt) = (xts ++ yus) :< yt
|
|
|
|
export
|
|
toSubst : AList m n -> Env Tm m n
|
|
toSubst [<] = Var
|
|
toSubst (xts :< (x , t)) = subst (toSubst xts) . (t `Auto.for` x)
|
|
|
|
record Unifying m where
|
|
constructor MkUnifying
|
|
{target : Nat}
|
|
rho : AList m target
|
|
-- TODO: add proofs too?
|
|
|
|
flexFlex : {m : _} -> (x, y : Fin m) -> Unifying m
|
|
flexFlex x y = case thicken x y of
|
|
EQ => MkUnifying [<]
|
|
NEQ y' => MkUnifying [<(x, Var y')]
|
|
|
|
flexRigid : {m : _} -> (x : Fin m) -> (t : Tm m) -> Maybe (Unifying m)
|
|
-- We only have two cases so that Idris can see that `m` ought to be S-headed
|
|
flexRigid x@FZ t = do
|
|
t' <- check x t
|
|
Just (MkUnifying [<(x,t')])
|
|
flexRigid x@(FS{}) t = do
|
|
t' <- check x t
|
|
Just (MkUnifying [<(x,t')])
|
|
|
|
export
|
|
mgu : {m : _} -> (s, t : Tm m) -> Maybe (Unifying m)
|
|
|
|
amgu : {n : _} -> (s, t : Tm m) -> AList m n -> Maybe (Unifying m)
|
|
amgus : {n : _} -> (s, t : List (Tm m)) -> AList m n -> Maybe (Unifying m)
|
|
|
|
mgu s t = amgu s t [<]
|
|
|
|
amgu (Con f ts) (Con g us) acc
|
|
= do guard (f == g)
|
|
amgus ts us acc
|
|
amgu (Var x) (Var y) [<] = Just (flexFlex x y)
|
|
amgu (Var x) t [<] = flexRigid x t
|
|
amgu s (Var y) [<] = flexRigid y s
|
|
amgu s t (rho :< (x, v)) = do
|
|
let sig = v `for` x
|
|
MkUnifying acc <- amgu (subst sig s) (subst sig t) rho
|
|
Just (MkUnifying (acc :< (x, v)))
|
|
|
|
amgus [] [] acc = Just (MkUnifying acc)
|
|
amgus (t :: ts) (u :: us) acc = do
|
|
MkUnifying acc <- amgu t u acc
|
|
amgus ts us acc
|
|
amgus _ _ _ = Nothing
|
|
|
|
------------------------------------------------------------------------------
|
|
-- End of the interlude
|
|
------------------------------------------------------------------------------
|
|
|
|
export
|
|
record Rule where
|
|
constructor MkRule
|
|
context : SnocList Name
|
|
ruleName : RuleName
|
|
{arity : Nat}
|
|
premises : Vect arity (Tm (length context))
|
|
conclusion : Tm (length context)
|
|
|
|
export
|
|
(.scope) : Rule -> Nat
|
|
r .scope = length r.context
|
|
|
|
export
|
|
Show Rule where
|
|
show (MkRule context nm premises conclusion)
|
|
= unlines
|
|
$ ifThenElse (null context) "" (" forall \{unwords (map show context <>> [])}.")
|
|
:: map ((" " ++) . showTerm context) (toList premises)
|
|
++ [ replicate 25 '-' ++ " " ++ show nm
|
|
, " " ++ showTerm context conclusion
|
|
]
|
|
|
|
public export
|
|
HintDB : Type
|
|
HintDB = List Rule
|
|
|
|
namespace Example
|
|
|
|
data Add : (m, n, p : Nat) -> Type where
|
|
AddBase : Add 0 n n
|
|
AddStep : Add m n p -> Add (S m) n (S p)
|
|
|
|
add : Vect 3 (Tm m) -> Tm m
|
|
add = Con (UName `{Search.Auto.Example.Add}) . toList
|
|
|
|
zro : Tm m
|
|
zro = Con (UName `{Prelude.Types.Z}) []
|
|
|
|
suc : Vect 1 (Tm m) -> Tm m
|
|
suc = Con (UName `{Prelude.Types.S}) . toList
|
|
|
|
qAddBase : Rule
|
|
qAddBase
|
|
= MkRule [< UN (Basic "n")] (Free `{AddBase})
|
|
[]
|
|
-------------------------------
|
|
$ add [ zro, Var FZ, Var FZ ]
|
|
|
|
qAddStep : Rule
|
|
qAddStep
|
|
= MkRule (UN . Basic <$> [<"m","n","p"]) (Free `{AddStep})
|
|
[add [Var 2, Var 1, Var 0]]
|
|
-----------------------------
|
|
$ add [suc [Var 2], Var 1, suc [Var 0]]
|
|
|
|
addHints : HintDB
|
|
addHints = [qAddBase, qAddStep]
|
|
|
|
|
|
||| A search Space represents the space of possible solutions to a problem.
|
|
||| It is a finitely branching, potentially infinitely deep, tree.
|
|
||| E.g. we can prove `Nat` using:
|
|
||| 1. either Z or S (finitely branching)
|
|
||| 2. arbitrarily many S constructor (unbounded depth)
|
|
public export
|
|
data Space : Type -> Type where
|
|
Solution : a -> Space a
|
|
Candidates : List (Inf (Space a)) -> Space a
|
|
|
|
||| A proof is a completed tree where each node is the invocation of a rule
|
|
||| together with proofs for its premises, or a lambda abstraction with a
|
|
||| subproof.
|
|
public export
|
|
data Proof : Type where
|
|
Call : RuleName -> List Proof -> Proof
|
|
Lam : Nat -> Proof -> Proof
|
|
|
|
export
|
|
Show Proof where
|
|
show prf = unlines (go "" [<] prf <>> []) where
|
|
go : (indent : String) -> SnocList String -> Proof -> SnocList String
|
|
go indent acc (Call r prfs) =
|
|
let acc = acc :< (indent ++ show r) in
|
|
assert_total $ foldl (go (" " ++ indent)) acc prfs
|
|
go indent acc (Lam n prf) =
|
|
let acc = acc :< (indent ++ "\\ x" ++ show n) in
|
|
assert_total $ go (" " ++ indent) acc prf
|
|
|
|
|
|
||| A partial proof is a vector of goals and a continuation which, provided
|
|
||| a proof for each of the goals, returns a completed proof
|
|
public export
|
|
record PartialProof (m : Nat) where
|
|
constructor MkPartialProof
|
|
leftovers : Nat
|
|
goals : Vect leftovers (Tm m)
|
|
continuation : Vect leftovers Proof -> Proof
|
|
|
|
||| Helper function to manufacture a proof step
|
|
export
|
|
apply : (r : Rule) -> Vect (r.arity + k) Proof -> Vect (S k) Proof
|
|
apply r args = let (premises, rest) = splitAt r.arity args in
|
|
Call r.ruleName (toList premises) :: rest
|
|
|
|
mkVars : (m : Nat) -> (vars : SnocList Name ** length vars = m)
|
|
mkVars Z = ([<] ** Refl)
|
|
mkVars m@(S m') = bimap (:< UN (Basic $ "_invalidName" ++ show m)) (\prf => cong S prf) (mkVars m')
|
|
|
|
solveAcc : {m : _} -> Nat -> HintDB -> PartialProof m -> Space Proof
|
|
solveAcc idx rules (MkPartialProof 0 goals k)
|
|
= Solution (k [])
|
|
solveAcc idx rules (MkPartialProof (S ar) (Con Arrow [a, b] :: goals) k)
|
|
-- to discharge an arrow type, we:
|
|
-- 1. Bind a new variable
|
|
-- 2. Add a new rule corresponding to that variable whose type is based on the function's domain
|
|
-- 3. Try to build an element of the codomain
|
|
= let (prems, res) = split a in
|
|
let (vars ** Refl) = mkVars m in
|
|
let rule = MkRule vars (Bound idx) (fromList prems) res in
|
|
Candidates [ solveAcc (S idx) (rule :: rules)
|
|
$ MkPartialProof (S ar) (b :: goals)
|
|
$ \ (b :: prfs) => k (Lam idx b :: prfs)]
|
|
solveAcc idx rules (MkPartialProof (S ar) (g :: goals) k)
|
|
= Candidates (assert_total $ map step rules) where
|
|
|
|
step : Rule -> Inf (Space Proof)
|
|
step r with (mgu (rename (weakenN r.scope) g) (rename (shift m) (conclusion r)))
|
|
_ | Nothing = Candidates []
|
|
_ | Just sol = let sig = toSubst sol.rho in
|
|
solveAcc idx rules $ MkPartialProof
|
|
(r.arity + ar)
|
|
(map (subst (sig . shift m)) r.premises
|
|
++ map (subst (sig . weakenN r.scope)) goals)
|
|
(k . apply r)
|
|
|
|
||| Solve takes a database of hints, a goal to prove and returns
|
|
||| the full search space.
|
|
export
|
|
solve : {m : _} -> Nat -> HintDB -> Tm m -> Space Proof
|
|
solve idx rules goal = solveAcc idx rules (MkPartialProof 1 [goal] head)
|
|
|
|
||| Depth first search strategy to explore a search space.
|
|
||| This is made total by preemptively limiting the depth the search
|
|
||| is willing to explore.
|
|
export
|
|
dfs : (depth : Nat) -> Space a -> List a
|
|
dfs _ (Solution x) = [x]
|
|
dfs 0 _ = []
|
|
dfs (S k) (Candidates cs) = cs >>= \ c => dfs k c
|
|
|
|
namespace Example
|
|
|
|
four : Maybe Proof
|
|
four = head'
|
|
$ dfs 5
|
|
$ solve {m = 0} 0 addHints
|
|
$ add [ suc [suc [zro]]
|
|
, suc [suc [zro]]
|
|
, suc [suc [suc [suc [zro]]]]
|
|
]
|
|
|
|
lengthDistributesOverFish : (sx : SnocList a) -> (xs : List a) ->
|
|
length (sx <>< xs) === length sx + length xs
|
|
lengthDistributesOverFish sx [] = sym $ plusZeroRightNeutral ?
|
|
lengthDistributesOverFish sx (x :: xs) = Calc $
|
|
|~ length ((sx :< x) <>< xs)
|
|
~~ length (sx :< x) + length xs ...( lengthDistributesOverFish (sx :< x) xs)
|
|
~~ S (length sx) + length xs ...( Refl )
|
|
~~ length sx + S (length xs) ...( plusSuccRightSucc ? ? )
|
|
~~ length sx + length (x :: xs) ...( Refl )
|
|
|
|
convertVar : (vars : SnocList Name) -> Name -> Tm (length vars)
|
|
convertVar [<] nm = Con (UName nm) []
|
|
convertVar (sx :< x) nm = if x == nm then Var FZ else go sx [x] FZ
|
|
|
|
where
|
|
|
|
go : (vars : SnocList Name) -> (ctxt : List Name) ->
|
|
Fin (length ctxt) -> Tm (length (vars <>< ctxt))
|
|
go [<] ctxt k = Con (UName nm) []
|
|
go (sx :< x) ctxt k =
|
|
if x == nm
|
|
then Var $ rewrite lengthDistributesOverFish sx (x :: ctxt) in
|
|
rewrite plusCommutative (length sx) (S (length ctxt)) in
|
|
weakenN (length sx) (FS k)
|
|
else go sx (x :: ctxt) (FS k)
|
|
|
|
skolemVar : SnocList Name -> Name -> Tm 0
|
|
skolemVar [<] nm = Con (UName nm) []
|
|
skolemVar (sx :< x) nm
|
|
= if nm == x then Con (Bound (length sx)) [] else skolemVar sx nm
|
|
|
|
getFnArgs : TTImp -> (TTImp, List TTImp)
|
|
getFnArgs = go [] where
|
|
|
|
go : List TTImp -> TTImp -> (TTImp, List TTImp)
|
|
go acc (IApp _ f t) = go (t :: acc) f
|
|
go acc (INamedApp _ f n t) = go acc f
|
|
go acc (IAutoApp _ f t) = go acc f
|
|
go acc t = (t, acc)
|
|
|
|
parameters (0 f : SnocList Name -> Nat) (cvar : (vars : SnocList Name) -> Name -> Tm (f vars))
|
|
|
|
||| Converts a type of the form (a -> (a -> b -> c) -> b -> c)
|
|
||| to our internal representation
|
|
export
|
|
convert : (vars : SnocList Name) -> TTImp -> Either TTImp (Tm (f vars))
|
|
convert vars (IVar _ nm) = pure (cvar vars nm)
|
|
convert vars t@(IApp x y z) = do
|
|
let (IVar _ nm, args) = getFnArgs t
|
|
| _ => Left t
|
|
let (Con nm []) = convertVar vars nm
|
|
| _ => Left t
|
|
Con nm <$> assert_total (traverse (convert vars) args)
|
|
convert vars t@(IPi _ _ ExplicitArg (Just n@(UN{})) argTy retTy) = Left t
|
|
convert vars (IPi _ _ ExplicitArg _ argTy retTy)
|
|
= do a <- convert vars argTy
|
|
b <- convert vars retTy
|
|
pure (Con Arrow [a,b])
|
|
convert vars t = Left t
|
|
|
|
||| Parse a type of the form
|
|
||| forall a b c. a -> (a -> b -> c) -> b -> c to
|
|
||| 1. a scope [<a,b,c]
|
|
||| 2. a representation of the rest
|
|
export
|
|
parseType : TTImp -> Either TTImp (vars : SnocList Name ** Tm (f vars))
|
|
parseType = go [<] where
|
|
|
|
go : SnocList Name -> TTImp -> Either TTImp (vars : SnocList Name ** Tm (f vars))
|
|
go vars (IPi _ _ ImplicitArg mn _ retTy)
|
|
= go (vars :< fromMaybe (UN Underscore) mn) retTy
|
|
go vars t = map (MkDPair vars) (convert vars t)
|
|
|
|
||| Parse a type, where bound variables are flexible variables
|
|
export
|
|
parseRule : TTImp -> Either TTImp (vars : SnocList Name ** Tm (length vars))
|
|
parseRule = parseType length convertVar
|
|
|
|
||| Parse a type, where bound variables are rigid variables
|
|
export
|
|
parseGoal : TTImp -> Either TTImp (SnocList Name, Tm 0)
|
|
parseGoal t = do
|
|
(vars ** g) <- parseType (\ _ => 0) skolemVar t
|
|
pure (vars, g)
|
|
|
|
export
|
|
mkRule : Name -> Elab Rule
|
|
mkRule nm = do
|
|
[(_, ty)] <- getType nm
|
|
| [] => fail "Couldn't find \{show nm}."
|
|
| nms => fail $ concat
|
|
$ "Ambiguous name \{show nm}, could be any of: "
|
|
:: intersperse ", " (map (show . fst) nms)
|
|
logMsg (show nm) 1 "Found the definition."
|
|
let Right (m ** tm) = parseRule ty
|
|
| Left t => fail "The type of \{show nm} is unsupported, chocked on \{show t}"
|
|
logMsg (show nm) 1 "Parsed the type."
|
|
let (premises, goal) = split tm
|
|
logMsg (show nm) 1 "Successfully split the type."
|
|
let r = MkRule m (Free nm) (fromList premises) goal
|
|
logMsg "auto.quoting.\{show nm}" 1 ("\n" ++ show r)
|
|
pure r
|
|
|
|
namespace Example
|
|
|
|
evenHints : HintDB
|
|
evenHints = with [Prelude.(::), Prelude.Nil]
|
|
[ %runElab (mkRule `{EvenZ})
|
|
, %runElab (mkRule `{EvenS})
|
|
, %runElab (mkRule `{EvenPlus})
|
|
]
|
|
|
|
export
|
|
getGoal : Elab (HintDB, Tm 0)
|
|
getGoal = do
|
|
Just gty <- goal
|
|
| Nothing => fail "No goal to get"
|
|
let Right (vars, qgty) = parseGoal gty
|
|
| Left t => fail "Couldn't parse goal type: \{show t}"
|
|
let (qass, qg) = split qgty
|
|
pure (toPremises (length vars) qass, qg)
|
|
|
|
where
|
|
toPremises : Nat -> List (Tm 0) -> HintDB
|
|
toPremises acc [] = []
|
|
toPremises acc (t :: ts)
|
|
= let (prems, res) = split t in
|
|
MkRule [<] (Bound acc) (fromList prems) res :: toPremises (S acc) ts
|
|
|
|
export
|
|
unquote : Proof -> TTImp
|
|
unquote (Call f xs)
|
|
= foldl (IApp emptyFC) (IVar emptyFC (toName f))
|
|
$ assert_total (map unquote xs)
|
|
unquote (Lam n prf)
|
|
= ILam emptyFC MW ExplicitArg (Just $ toName (Bound n)) (Implicit emptyFC False)
|
|
$ unquote prf
|
|
|
|
export
|
|
intros : List a -> TTImp -> TTImp
|
|
intros = go 0 where
|
|
|
|
go : Nat -> List a -> TTImp -> TTImp
|
|
go idx [] t = t
|
|
go idx (_ :: as) t
|
|
= ILam emptyFC MW ExplicitArg (Just $ toName (Bound idx)) (Implicit emptyFC False)
|
|
$ go (S idx) as t
|
|
|
|
export
|
|
bySearch : (depth : Nat) -> HintDB -> Elab a
|
|
bySearch depth rules = do
|
|
(rules', g) <- getGoal
|
|
logMsg "auto.search.goal" 1 (showTerm [<] g)
|
|
let rules = rules ++ rules'
|
|
logMsg "auto.search.rules" 1 (unlines $ map show rules')
|
|
let (prf :: _) = dfs depth (solve (length rules') rules g)
|
|
| _ => fail "Couldn't find proof for goal"
|
|
check (intros rules' (unquote prf))
|
|
|
|
namespace Example
|
|
|
|
idTest : a -> a
|
|
idTest = %runElab (bySearch 2 [])
|
|
|
|
constTest : a -> b -> a
|
|
constTest = %runElab (bySearch 2 [])
|
|
|
|
sTest : (a -> b -> c) -> (a -> b) -> a -> c
|
|
sTest = %runElab (bySearch 4 [])
|
|
|
|
-- The type of `MkPair` is falsely dependent and makes the machinery choke
|
|
-- so we define this one instead
|
|
mkPair : a -> b -> Pair a b
|
|
mkPair a b = (a, b)
|
|
|
|
listFromDupTest : (a -> (a -> (a, a)) -> List a) -> a -> List a
|
|
listFromDupTest = %runElab (bySearch 6 [%runElab (mkRule `{mkPair})])
|
|
|
|
even0Test : Even Z
|
|
even0Test = %runElab (bySearch 1 evenHints)
|
|
|
|
even2Test : Even 2
|
|
even2Test = %runElab (bySearch 2 evenHints)
|
|
|
|
evenPlusTest : Even n -> Even m -> Even (m + n)
|
|
evenPlusTest = %runElab (bySearch 3 evenHints)
|
|
|
|
evenPlus2Test : Even m -> Even (m + 2)
|
|
evenPlus2Test = %runElab (bySearch 4 evenHints)
|
|
|
|
addBaseTest : Add Z Z Z
|
|
addBaseTest = %runElab (bySearch 3 addHints)
|
|
|
|
add2TwiceTest : Add 2 2 4
|
|
add2TwiceTest = %runElab (bySearch 3 addHints)
|