Quantifiers and

The last stretch of core.alg is the quantifiers, and . They follow the same build one / use one pattern as the connectives, with one twist: their rules carry a motive — a predicate T Prop naming the property you’re quantifying. We’ll work over an abstract sort T and an abstract predicate P : T Prop, so nothing is hidden.

For all:

To prove x. P(x), prove P(x) for a fresh, arbitrary x. forall_intro hands you that eigenvariable — one premise, so then, and the then carries x into its context. The one property we can prove of every element with no assumptions is that it equals itself:

import core(forall_intro, refl);

sort T : Sort;

lemma everything_is_itself
    (x : T) st x = x;
proof
  by forall_intro(T, _ = _) then x : T  x = x; by refl(T, x);
qed;

Here the motive _ = _ is the predicate λ (x : T) st x = x (that _ sugar from Induction and friends). Because x was introduced fresh, proving x = x for it counts as proving it for everyone.

To use a , instantiate it at a specific term. forall_elim takes y. P(y) and a point a and yields P(a). This time the motive is the abstract P itself, and the universal fact is a lemma parameter:

import core(forall_elim);

sort T : Sort;

lemma at_a_point(P : T  Prop, a : T, all :=  (y : T) st P(y))
   P(a);
proof
  by forall_elim(T, P, a) then   (y : T) st P(y); by all;
qed;

The third argument, a, is the point you’re instantiating at; the then goal is the universal statement you’re drawing it from, discharged by all.

There exists:

Building a means producing a witness. exists_intro takes a term a and a proof that the property holds of that term:

import core(exists_intro);

sort T : Sort;

lemma there_is_one(P : T  Prop, a : T, pa := P(a))
    (x : T) st P(x);
proof
  by exists_intro(T, P, a) then  P(a); by pa;
qed;

We offered a as the witness, so the leftover goal is the property at aP(a) — which our assumption pa supplies.

Using a is the dual of using a : you get a witness but you don’t get to know which one, so whatever you conclude must hold no matter who it is. exists_elim hands you a fresh x and the hypothesis witness := P(x) (named, as always, after the rule’s premise), and asks you to reach your goal from there — two premises, cases. To show it really gives you something usable, we unpack an existential and immediately repack it:

import core(exists_intro, exists_elim);

sort T : Sort;

lemma repack(P : T  Prop, ex :=  (x : T) st P(x))
    (x : T) st P(x);
proof
  by exists_elim(T, P,  (x : T) st P(x)) cases
    case   (x : T) st P(x); by ex; qed;
    case x : T; witness := P(x)   (x : T) st P(x);
      by exists_intro(T, P, x) then  P(x); by witness;
    qed;
  qed;
qed;

The second branch pulls out the witness x and the proof witness := P(x), then feeds them straight back into exists_intro. Trivial as a theorem, but it shows the exact shape every real exists_elim proof has.

Your turn

Combine the two moves: from y. P(y)P holds everywhere — produce a proof that x. P(x).

import core(forall_elim, exists_intro);

sort T : Sort;

lemma somewhere(P : T  Prop, a : T, all :=  (y : T) st P(y))
    (x : T) st P(x);
proof
  by wip(?goal);
wip;

Hint

Witness the existential at a first: by exists_intro(T, P, a) leaves then P(a);. Get P(a) by instantiating the universal — by forall_elim(T, P, a) then (y : T) st P(y); by all;.

If and only if:

A biconditional is just two implications bundled together, and its rules say exactly that. biconditional_intro asks for both directions — A B and B A — so two premises, cases, each closed by an assumed implication:

import core(biconditional_intro);

lemma equivalent(A B : Prop, fwd := A  B, bwd := B  A)
   A  B;
proof
  by biconditional_intro(A, B) cases
    case  A  B; by fwd; qed;
    case  B  A; by bwd; qed;
  qed;
qed;

Going the other way, biconditional_elim_left extracts A B from A B (and biconditional_elim_right extracts B A) — one premise each, so then. Between them you can take a apart into whichever implication you need, then finish with implication_elim from The logical toolkit.

That’s all of core. Next we leave pure logic behind and start reasoning about data.