idris-1.3.0: libs/pruviloj/Pruviloj/Disjoint.idr
||| Provides a tactic for solving constructor disjointness goals.
module Pruviloj.Disjoint
import Language.Reflection.Utils
import Pruviloj.Core
import Pruviloj.Internals
import Pruviloj.Renamers
%default total
%access private
-------------------
-- PRIVATE GUTS --
-------------------
||| Compute the name to use for a disjointness lemma.
disjointName : TTName -> TTName -> TTName
disjointName l r = NS (SN (MetaN (UN "disjoint") (SN (MetaN l r))))
["Disjoint", "Pruviloj"]
||| Compute the type to use for a disjointness lemma.
covering
disjointnessType : (l, r : TTName) -> Elab (TTName, List FunArg, Raw)
disjointnessType l r =
do (l', DCon _ _, lty) <- lookupTyExact l
| _ => notConstructor l
(r', DCon _ _, rty) <- lookupTyExact r
| _ => notConstructor r
let fn = disjointName l' r'
when (l' == r') $
fail [ NamePart l', TextPart "and"
, NamePart r', TextPart "are clearly not disjoint!"
]
(argsl, resl) <- stealBindings !(forget lty) noRenames
(argsr, resr) <- stealBindings !(forget rty) noRenames
let args = map {b=FunArg}
(\(n, b) => MkFunArg n (binderTy b) Implicit NotErased)
(argsl ++ argsr)
let eq : Raw = `((=) {A=~resl}
{B=~resr}
~(mkApp (Var l') (map (Var . fst) argsl))
~(mkApp (Var r') (map (Var . fst) argsr)))
h <- gensym "h"
pure (fn, args ++ [MkFunArg h eq Explicit NotErased], `(Void))
where
notConstructor : TTName -> Elab a
notConstructor c = fail [NamePart c, TextPart "is not a constructor"]
||| Return the name of the disjointness lemma for two constructors,
||| defining it if necessary.
|||
||| @ l one of the constructor names
||| @ r the other constructor name
covering
getDisjointness : (l, r : TTName) -> Elab TTName
getDisjointness l r = exists <|> declare
where exists : Elab TTName
exists = do (yep, _, _) <- lookupTyExact (disjointName l r)
pure yep
covering
declare : Elab TTName
declare = do (fn, args, ret) <- disjointnessType l r
declareType $ Declare fn args ret
defineFunction $ DefineFun fn []
pure fn
----------------------
-- PUBLIC INTERFACE --
----------------------
||| Solve a goal of the form `(C1 x1 x2 ... xn = C2 x1 x2 ... xn) ->
||| Void` for disjoint constructors `C1` and `C2`.
public export covering
disjoint : Elab ()
disjoint =
do compute
g <- snd <$> getGoal
case g of
`(((=) {A=~aTy} {B=~bTy} ~a ~b) -> Void) =>
do Just lHead <- headName <$> forget a
| Nothing => fail [TermPart a, TextPart "doesn't have a name at the head"]
Just rHead <- headName <$> forget b
| Nothing => fail [TermPart b, TextPart "doesn't have a name at the head"]
[] <- refine (Var !(getDisjointness lHead rHead))
| _ => fail [TextPart "Didn't solve argument to disjointness lemma"]
skip
ty =>
fail [NamePart `{disjoint}, TextPart "is not applicable to goal", TermPart ty]
||| Solve a goal when there is an hypothesis of the form
||| `(C1 x1 x2 ... xn = C2 x1 x2 ... xn)`
||| for disjoint constructors `C1` and `C2`.
||| Similar to `discriminate` in Coq.
public export covering
discriminate : Elab ()
discriminate =
do compute
g <- goalType
-- for each thing in the context, until we find one that succeeds
flip choiceMap !getEnv $ \(n, b) =>
do -- 1) check if it is an equality
`(((=) {A=~aTy} {B=~bTy} ~a ~b)) <- forget (binderTy b)
| _ => fail [TextPart "Not equality type"]
-- 2) check if both sides of the equality have heads
case (headName a, headName b) of
(Just lHead, Just rHead) =>
do lemma <- Var <$> getDisjointness lHead rHead
-- 3) learn how many arguments the lemma takes
(_, x :: xs, _) <- disjointnessType lHead rHead
| _ => fail [TextPart "Disjointness type takes no argument"]
-- 4) try to apply and focus on the last hole
apply `(void {a=~g}) [False]
solve
-- 5) try to call the disjointness lemma
-- We only want to pass the last arg explicitly to the lemma
let bools = map (const True) xs ++ [False]
(h :: hs) <- apply lemma bools
| _ => fail [TextPart "Disjointness lemma produces no holes"]
solve
-- 6) try to pass the current equality as an argument
focus (last (h :: hs))
exact (Var n)
_ => fail [TextPart "Equality sides don't have a name at the head"]