5 ntac: The New Tactic System
As Coq has shown, tactics have proven useful for doing complex proofs. In Cur, tactics are not built-in or provided by the language. However, any user can use meta-programming to add tactics to Cur. In fact, a user did. Cur originally shipped with a proof-of-concept tactic system, but the system did not scale well. So Jay designed and prototyped a new one over a weekend in 200 lines of code. Now it’s the default system.
5.1 The ntac system
(require cur/ntac/base) | package: cur-lib |
A tactic is used at the top-level of a proof script. A tactic is a Racket function that satisfies the contact (r:-> nttz? nttz?) (where r:-> is the Racket function contract normally written ->; we use r:-> since Cur redefined ->). Tactics easily compose, may navigate the proof tree, resolve multiple holes, and be called recursively.
A tactical is used to manipulate the focus of proof tree, such as to resolve a single hole. A tactical satisfies the contract (r:-> dict? ntt?) We will conflate tacticals with functions that produce tacticals from addition arguments. I.e. we also call functions that satisfy (r:-> syntax? ... (r:-> dict? ntt?)) tacticals. Tacticals receive additional arguments as Cur syntax; for technical reasons these need to be processed by ntac-syntax before being used in a tactical. Tacticals are usually used by the fill tactic, which simply applies a tactical to the current focus of the proof tree zipper. We usually provide macros for the surface syntax of tacticals so that users need not deal with syntax objects directly, or explicitly use the fill tactic. These macros follow the naming convention by-_tactical.
5.1.1 Usage
syntax
(ntac type tactic ...)
> ((ntac (forall (x : Nat) Nat) by-intro by-assumption) z) (z)
> ((ntac (forall (x : Nat) Nat) interactive) z) (z)
syntax
(define-theorem name ty ps ...)
> (define-theorem nat-id (forall (x : Nat) Nat) by-intro by-assumption)
5.1.2 Base Tactics
These tactics are used in the tactic system API, while other other tactics are defined outside the base system.
value
next : tactic?
5.1.3 Proof Trees
TODO: The internal details of the tactic system should be de-emphasized, and the tactics that programmers actually want to use should be more prominent. These next few subsections should probably be moved to the end of the documentation.
The ntac proof tree datatype ntt represents a Cur term with holes. Specifically, the proof tree contains nodes for a hole, an exact term, a combination of subterms, and a context manipulation.
struct
(struct ntt-hole ntt (contains-hole? goal) #:transparent) contains-hole? : boolean? goal : syntax?
procedure
(make-ntt-hole goal) → ntt?
goal : syntax?
struct
(struct ntt-exact ntt (contains-hole? goal term) #:transparent) contains-hole? : boolean? goal : syntax? term : syntax?
procedure
(make-ntt-exact goal term) → ntt?
goal : syntax? term : syntax?
struct
(struct ntt-context ntt ( contains-hole? goal env-transformer subtree) #:transparent) contains-hole? : boolean? goal : syntax? env-transformer : (r:-> dict? dict?) subtree : ntt?
procedure
(make-ntt-context env-transformer subtree) → ntt?
env-transformer : (r:-> dict? dict?) subtree : ntt?
struct
(struct ntt-apply ntt (contains-hole? goal subtrees f) #:transparent) contains-hole? : boolean? goal : syntax? subtrees : (listof ntt?) f : (r:-> syntax? ... syntax?)
procedure
(make-ntt-apply goal subtrees f) → ntt?
goal : syntax? subtrees : (listof ntt) f : (r:-> syntax? ... syntax?)
struct
(struct ntt-done ntt (contains-hole? goal subtree) #:transparent) contains-hole? : boolean? goal : syntax? subtree : ntt?
procedure
(make-ntt-done subtree) → ntt?
subtree : ntt?
5.1.4 Proof Tree Zipper
To navigate the proof tree, we define the ntac tree zipper.
procedure
(nttz-down-context nttz) → nttz?
nttz : nttz?
procedure
(nttz-down-apply nttz) → nttz?
nttz : nttz?
procedure
(nttz-done? nttz) → boolean?
nttz : nttz?
5.1.5 Tactic System API
procedure
(new-proof-tree goal) → ntt?
goal : syntax?
procedure
(proof-tree->complete-term pt [err-stx]) → syntax?
pt : ntt? err-stx : syntax? = #f
procedure
(eval-proof-step ptz pstep) → nttz?
ptz : nttz? pstep : syntax?
procedure
(eval-proof-script pt psteps [err-stx]) → ntt?
pt : ntt? psteps : (listof syntax?) err-stx : syntax? = #f
procedure
(ntac-syntax stx) → syntax?
stx : syntax?
5.1.6 theorem-info
struct
(struct theorem-info identifier-info (name orig))
name : identifier? orig : syntax?
5.2 Standard Tactics and Tacticals
(require cur/ntac/standard) | package: cur-lib |
struct
(struct exn:fail:ntac exn:fail ())
struct
procedure
(raise-ntac-goal-exception msgf arg ...) → any
msgf : string? arg : string?
syntax
(ntac-match goal [pattern branch] ...)
value
nop : tactic?
value
display-focus : tactic?
> ((ntac (forall (x : Nat) Nat) display-focus by-intro display-focus by-assumption) z)
--------------------------------
(Π (x : Nat) Nat)
x : Nat
--------------------------------
Nat
(z)
procedure
(try t) → tactic?
t : tactic?
> ((ntac (forall (x : Nat) Nat) by-assumption) z) could not find matching assumption for goal
#<syntax:eval:5:0 (real-Π (x : Nat) Nat)>
> ((ntac (forall (x : Nat) Nat) (try by-assumption)) z) eval:6:0: qed: Proof incomplete.
in: ((try by-assumption))
procedure
(fill t) → tactic?
t : tactical?
procedure
(intro [name]) → tactical?
name : identifier? = #f
> ((ntac (forall (x : Nat) Nat) (fill (intro)) by-assumption) z) (z)
> ((ntac (forall (x : Nat) Nat) (fill (intro (ntac-syntax #'x))) by-assumption) z) (z)
syntax
(by-intros id ...)
value
assumption : tactical?
syntax
value
obvious : tactical?
value
by-obvious : tactic?
syntax
(by-assert name thm)
> ((ntac (∀ [x : Nat] [y : Nat] (-> (== Nat x y) (== Nat y x))) (by-intros x y x=y) ; define local thm named y=x (by-assert y=x (== Nat y x)) ; prove y=x display-focus (by-rewriteR x=y) display-focus reflexivity ; prove original goal using y=x display-focus (by-assumption y=x)) 1 1 (refl Nat 1))
x : Nat
y : Nat
x=y : (== Nat x y)
--------------------------------
(== Nat y x)
x : Nat
y : Nat
x=y : (== Nat x y)
--------------------------------
(== Nat y y)
x : Nat
y : Nat
x=y : (== Nat x y)
y=x : (== Nat y x)
--------------------------------
(== Nat y x)
(PM-refl (Nat) (s (z)))
procedure
(assert name thm) → tactical?
name : identifier? thm : syntax?
value
simpl : tactic?
> (define-theorem plus_1_l (∀ [n : Nat] (== Nat (plus 1 n) (s n))) by-intro display-focus simpl display-focus reflexivity)
n : Nat
--------------------------------
(== Nat (plus 1 n) (s n))
n : Nat
--------------------------------
(== Nat (s n) (s n))
syntax
(by-destruct name)
(by-destruct name #:as param-namess)
> (define-theorem plus-1-neq-0 (∀ [n : Nat] (== Bool (nat-equal? (plus 1 n) 0) false)) (by-intro n) display-focus (by-destruct n #:as [() (n-1)]) ; zero case display-focus simpl display-focus reflexivity ; succ case display-focus simpl display-focus reflexivity)
n : Nat
--------------------------------
(== Bool (nat-equal? (plus 1 n) 0) false)
n : Nat
--------------------------------
(== Bool (nat-equal? (plus 1 z) 0) false)
n : Nat
--------------------------------
(== Bool false false)
n : Nat
n-1 : Nat
--------------------------------
(== Bool (nat-equal? (plus 1 (s n-1)) 0) false)
n : Nat
n-1 : Nat
--------------------------------
(== Bool false false)
> (plus-1-neq-0 0) (PM-refl (Bool) (false))
> (plus-1-neq-0 1) (PM-refl (Bool) (false))
procedure
(destruct name [param-namess]) → tactical?
name : identifier? param-namess : syntax? = #f
The resulting proof term uses match.
param-namess should be a list of list of identifiers, which are used as the binders in each clause. If not specified, the original binder names from the data declaration are used. Does not include induction hypotheses for recursive arguments.
syntax
(by-destruct/elim name)
(by-destruct/elim name #:as param-namess)
> (define negb (λ [b : Bool] (new-elim b (λ [b : Bool] Bool) false true))) > (negb true) (false)
> (negb false) (true)
> (negb (negb true)) (true)
> (negb (negb false)) (false)
> (define-theorem negb-invol (forall [b : Bool] (== Bool (negb (negb b)) b)) (by-intro b) (by-destruct/elim b) ; true case display-focus simpl display-focus reflexivity ; false case display-focus simpl display-focus reflexivity)
--------------------------------
(== Bool (negb (negb true)) true)
--------------------------------
(== Bool true true)
--------------------------------
(== Bool (negb (negb false)) false)
--------------------------------
(== Bool false false)
procedure
(destruct/elim name [param-namess]) → tactical?
name : identifier? param-namess : syntax? = #f
The resulting proof term uses new-elim.
param-namess should be a list of list of identifiers, which are used as the binders in each clause. If not specified, the original binder names from the data declaration are used.
syntax
(by-induction name #:as param-namess)
> (define-theorem plus-n-0 (∀ [n : Nat] (== Nat n (plus n z))) (by-intro n) simpl (by-induction n #:as [() (n-1 IH)]) ; zero case display-focus reflexivity ; succ case display-focus simpl display-focus (by-rewriteL IH) display-focus reflexivity)
--------------------------------
(== Nat z (new-elim z (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x)))))
n-1 : Nat
IH : (== Nat n-1 (new-elim n-1 (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x)))))
--------------------------------
(== Nat (s n-1) (new-elim (s n-1) (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x)))))
n-1 : Nat
IH : (== Nat n-1 (new-elim n-1 (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x)))))
--------------------------------
(== Nat (s n-1) (s (new-elim n-1 (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x))))))
n-1 : Nat
IH : (== Nat n-1 (new-elim n-1 (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x)))))
--------------------------------
(== Nat (s n-1) (s n-1))
procedure
(induction name param-namess) → tactical?
name : identifier? param-namess : syntax?
Unlike destruct, induction binds an induction hypothesis for recursive arguments in each case.
The resulting proof term uses new-elim.
param-namess should be a list of list of identifiers, which are used as the binders in each clause. If not specified, the original binder names from the data declaration are used.
5.2.1 Interactive Tactic
In Cur, interactivity is just a user-defined tactic.
value
interactive : tactic?
5.3 Rewrite-related Tactics
(require cur/ntac/rewrite) | package: cur-lib |
Ntac includes two libraries of rewrite tactics: one (the above) for "standard" Paulin-Mohring equality (ie, ==), and another (cur/ntac/ML-rewrite) for Martin-Lof equality (i.e., ML-=).
Each library provides versions of the following bindings.
value
reflexivity : tactic?
syntax
(by-rewrite name . es)
Rewrites from right-to-left (equivalent to Coq’s ->), i.e., for a theorem (== A x y), x is replaced with y.
Short hand for (fill (rewrite #'name #:es #'es)).
Equivalent to by-rewriteR.
> (define-theorem identity-fn-applied-twice (∀ [f : (-> Bool Bool)] (-> (∀ [x : Bool] (== Bool (f x) x)) (∀ [b : Bool] (== Bool (f (f b)) b)))) (by-intros f H b) display-focus (by-rewrite H b) display-focus (by-rewrite H b) display-focus reflexivity)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) x))
b : Bool
--------------------------------
(== Bool (f (f b)) b)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) x))
b : Bool
--------------------------------
(== Bool (f b) b)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) x))
b : Bool
--------------------------------
(== Bool b b)
syntax
(by-rewriteR name . es)
Rewrites from right-to-left (equivalent to Coq’s ->), i.e., for a theorem (== A x y), x is replaced with y.
Short hand for (fill (rewrite #'name #:es #'es)).
Equivalent to by-rewrite.
syntax
(by-rewriteL name . es)
Short hand for (fill (rewrite #'name #:es #'es #:left? #t)).
Rewrites from left-to-right (equivalent to Coq’s <-), i.e., for a theorem (== A x y), y is replaced with x.
syntax
(by-rewrite/thm thm . es)
Rewrites from right-to-left (equivalent to Coq’s ->), i.e., for a theorem (== A x y), x is replaced with y.
(let ([thm-info (syntax-local-eval #'thm)]) (fill (rewrite #'thm #:thm-name (theorem-info-name thm-info) #:thm (theorem-info-orig thm-info) #:es #'es)))
> (define-theorem negb-invol (forall [b : Bool] (== Bool (not (not b)) b)) (by-intro b) (by-destruct/elim b) simpl reflexivity simpl reflexivity)
> (define-theorem not-applied-twice (∀ [f : (-> Bool Bool)] (-> (∀ [x : Bool] (== Bool (f x) (not x))) (∀ [b : Bool] (== Bool (f (f b)) b)))) (by-intros f H b) display-focus (by-rewrite H b) display-focus (by-rewrite H (not b)) display-focus (by-rewrite/thm negb-invol b) display-focus reflexivity)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) (not x)))
b : Bool
--------------------------------
(== Bool (f (f b)) b)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) (not x)))
b : Bool
--------------------------------
(== Bool (f (not b)) b)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) (not x)))
b : Bool
--------------------------------
(== Bool (not (not b)) b)
f : (-> Bool Bool)
H : (∀ (x : Bool) (== Bool (f x) (not x)))
b : Bool
--------------------------------
(== Bool b b)
syntax
(by-rewriteR/thm thm . es)
syntax
(by-rewriteL/thm thm . es)
Rewrites from left-to-right (equivalent to Coq’s <-), i.e., for a theorem (== A x y), y is replaced with x.
(let ([thm-info (syntax-local-eval #'thm)]) (fill (rewrite #'thm #:thm-name (theorem-info-name thm-info) #:thm (theorem-info-orig thm-info) #:left? #t #:es #'es)))
syntax
(by-rewrite/thm/normalized thm . es)
Rewrites from right-to-left (equivalent to Coq’s ->), i.e., for a theorem (== A x y), x is replaced with y.
(let ([thm-info (syntax-local-eval #'thm)]) (fill (rewrite #'thm #:thm-name (theorem-info-name thm-info) #:thm (cur-reflect (identifier-info-type thm-info)) #:es #'es)))
> (define-theorem plus-n-0 (∀ [n : Nat] (== Nat n (plus n z))) (by-intro n) simpl (by-induction n #:as [() (n-1 IH)]) ; zero case reflexivity ; succ case simpl (by-rewriteL IH) reflexivity)
> (define-theorem plus-n-Sm (∀ [n : Nat] [m : Nat] (== Nat (s (plus n m)) (plus n (s m)))) (by-intro n) (by-intro m) simpl (by-induction n #:as [() (n-1 IH)]) ; zero case simpl reflexivity ; succ case simpl (by-rewrite IH) reflexivity)
> (define-theorem plus_comm (∀ [n : Nat] [m : Nat] (== Nat (plus n m) (plus m n))) (by-intro n) (by-intro m) simpl (by-induction n #:as [() (n-1 IH)]) ; zero case simpl display-focus (by-rewriteL/thm/normalized plus-n-0 m) display-focus reflexivity ; succ case simpl (by-rewriteL/thm/normalized plus-n-Sm m n-1) (by-rewrite IH) reflexivity)
m : Nat
--------------------------------
(== Nat m (new-elim m (λ (anon : Nat) Nat) z (λ (x : Nat) (λ (ih-x : Nat) (s ih-x)))))
m : Nat
--------------------------------
(== Nat m m)
syntax
(by-rewriteR/thm/normalized thm . es)
syntax
(by-rewriteL/thm/normalized thm . es)
Rewrites from left-to-right (equivalent to Coq’s <-), i.e., for a theorem (== A x y), y is replaced with x.
(let ([thm-info (syntax-local-eval #'thm)]) (fill (rewrite #'thm #:thm-name (theorem-info-name thm-info) #:thm (cur-reflect (identifier-info-type thm-info)) #:left? #t #:es #'es)))
procedure
(rewrite name [ #:left? left? #:es es #:thm-name thm-name #:thm thm]) → tactical? name : identifier? left? : boolean? = #f es : syntax? = #'() thm-name : identifier? = #f thm : syntax? = #f
By default, rewrites from right-to-left (equivalent to Coq’s ->), i.e., for a theorem (== A x y), x is replaced with y. If left? is #t, rewrites from left-to-right instead (Coq’s <-).
syntax
(by-replace ty from to)
Replaces instances of from in the goal, which has type ty, with to. Adds ty as a subgoal.
> (define-theorem plus-assoc (∀ [n : Nat] [m : Nat] [p : Nat] (== Nat (plus n (plus m p)) (plus (plus n m) p))) (by-intros n m p) simpl (by-induction n #:as [() (n-1 IH)]) ; zero case reflexivity ; succ case simpl (by-rewrite IH) reflexivity)
> (define-theorem plus-swap (∀ [n : Nat] [m : Nat] [p : Nat] (== Nat (plus n (plus m p)) (plus m (plus n p)))) (by-intros n m p) (by-rewrite/thm plus-assoc n m p) display-focus (by-replace Nat (plus n m) (plus m n)) display-focus (by-rewriteL/thm plus-assoc m n p) reflexivity ; proof of by-replace theorem display-focus (by-rewrite/thm plus_comm m n) display-focus reflexivity)
n : Nat
m : Nat
p : Nat
--------------------------------
(== Nat (plus (plus n m) p) (plus m (plus n p)))
n : Nat
m : Nat
p : Nat
--------------------------------
(== Nat (plus (plus m n) p) (plus m (plus n p)))
n : Nat
m : Nat
p : Nat
--------------------------------
(== Nat (plus m n) (plus n m))
n : Nat
m : Nat
p : Nat
--------------------------------
(== Nat (plus n m) (plus n m))
with-limit: out of time