type_proof/
proof.rs

1//! Encodes proofs as types.
2//!
3//! # Proof system
4//!
5//! We a Hilbert proof system known as [<code>P<sub>2</sub></code>](https://en.wikipedia.org/wiki/Hilbert_system#Schematic_form_of_P2)
6//! (no relation to our propositional variable type alias [`P2`]).
7//!
8//! We write `⊢ P` to mean that our proof system proves the formula `P`. We also say that `P` is a *theorem*.
9//!
10//! ## Axioms
11//!
12//! We have three axiom schemas: for all formulas `P, Q, R`,
13//!
14//! 1) `⊢ P -> (Q -> P)`
15//! 2) `⊢ (P -> (Q -> R)) -> ((P -> Q) -> (P -> R))`
16//! 3) `⊢ (¬Q -> ¬P) -> (P -> Q)`
17//!
18//! (Technical point: we have omitted the parentheses round the outer `->` clauses to improve readability.)
19//!
20//! These respectively have the type aliases [`Axiom1<P, Q>`], [`Axiom2<P, Q, R>`] and [`Axiom3<P, Q>`], and are
21//! encompassed by the [`Axiom`] trait.
22//!
23//! ## Inference rules
24//!
25//! We have one inference rule, called [modus ponens](https://en.wikipedia.org/wiki/Modus_ponens):
26//!
27//! For all formulas `P, Q`, if `⊢ P` and `⊢ (P -> Q)`, then `⊢ Q`.
28//!
29//! This is given by the type [`MP<PrP, PrI>`]. `PrP` is a proof of `P`, and `PrI` is a proof of `(P -> Q)`. We
30//! actually allow [`MP`] to take in proofs of `P` and `(P -> Q)` instead of just the statements themselves to
31//! allow breaking proofs down into smaller theorems. This doesn't affect the validity of proofs or what can/can't
32//! be proved, since a full formal proof can be obtained by just substituting the proofs in.
33//!
34//! ## Proofs
35//!
36//! A proof of a formula `P` is a sequence of statements where:
37//! - Each statement is of the form `⊢ Q` for some formula `Q`.
38//! - Each statement is either an axiom or follows from two previous statements (not necessarily immediately
39//!   previous) via modus ponens.
40//! - The final statement is `⊢ P`.
41//!
42//! This is represented by the trait [`Proof`]. A valid proof of a [`Formula`] `P` will implement [`Proof`] with
43//! [`Proof::Proves`] equal to `P`; an invalid proof will not. The [`assert_proves`] function is provided as an easy
44//! way to check that a type implements [`Proof`] with the correct [`Proof::Proves`]. See below for an example.
45//!
46//! ## Example
47//!
48//! Proof that for any formula `P`, we have `⊢ P -> P`:
49//!
50//! ```text
51//! 1. ⊢ P -> ((p0 -> P) -> P)                                      [Axiom 1 with P and (p0 -> P)]
52//! 2. ⊢ (P -> ((p0 -> P) -> P)) -> ((P -> (p0 -> P)) -> (P -> P))  [Axiom 2 with P, (p0 -> P) and P]
53//! 3. ⊢ (P -> (p0 -> P)) -> (P -> P)                               [MP on 1. and 3.]
54//! 4. ⊢ P -> (p0 -> P)                                             [Axiom 1 with P and p0]
55//! 5. ⊢ P -> P                                                     [MP on 4. and 3.]
56//! ```
57//!
58//! This can be represented in our types as follows:
59//!
60//! ```
61//! use type_proof::{
62//!     formula::{Formula, Implies, P0},
63//!     proof::{Axiom1, Axiom2, MP, assert_proves},
64//! };
65//!
66//! type ToProve<P> = Implies<P, P>;
67//!
68//! type Step1<P> = Axiom1<P, Implies<P0, P>>;
69//! type Step2<P> = Axiom2<P, Implies<P0, P>, P>;
70//! type Step3<P> = MP<Step1<P>, Step2<P>>;
71//! type Step4<P> = Axiom1<P, P0>;
72//! type Step5<P> = MP<Step4<P>, Step3<P>>;
73//!
74//! // The fact that this type checks means that the proof is valid for any formula P
75//! fn for_all<P: Formula>() {
76//!     assert_proves::<Step5<P>, ToProve<P>>();
77//! }
78//! ```
79//!
80//! An invalid proof will throw a type checker error:
81//!
82//! ```compile_fail
83//! use type_proof::{
84//!     formula::{Formula, Implies},
85//!     proof::{Axiom1, MP, assert_proves},
86//! };
87//!
88//! type ToProve<P> = Implies<P, P>;
89//!
90//! // ⊢ P -> (P -> P)
91//! type Step1<P> = Axiom1<P, P>;
92//! // Deduce ⊢ P -> P from ⊢ P and ⊢ P -> (P -> P)
93//! // This may not be valid as we don't necessarily have ⊢ P
94//! type Step2<P> = MP<P, Step1<P>>;
95//!
96//! fn for_all<P: Formula>() {
97//!     // Fails to compile
98//!     assert_proves::<Step2<P>, ToProve<P>>();
99//! }
100//! ```
101//!
102//! It's not true that this proof is valid for all `P`, because it requires `⊢ P`, and there are `P`s for which
103//! that doesn't hold. But, this is valid if `P` is an axiom:
104//!
105//! ```
106//! # use type_proof::{
107//! #     formula::{Formula, Implies},
108//! #     proof::{Axiom1, MP, assert_proves},
109//! # };
110//! #
111//! # type ToProve<P> = Implies<P, P>;
112//! #
113//! # type Step1<P> = Axiom1<P, P>;
114//! # type Step2<P> = MP<P, Step1<P>>;
115//! #
116//! use type_proof::proof::Axiom;
117//!
118//! fn for_all<P: Axiom>() {
119//!     assert_proves::<Step2<P>, ToProve<P>>();
120//! }
121//! ```
122//!
123//! [`P2`]: crate::formula::P2
124
125use std::marker::PhantomData;
126
127use crate::formula::{Formula, Implies, Not};
128
129/// A sequence of statements that prove a formula.
130///
131/// See the [module documentation](self) for more info.
132pub trait Proof {
133    /// The formula that this proof proves.
134    type Proves: Formula;
135
136    /// How many statements this proof involves.
137    const LENGTH: usize;
138
139    /// Displays the proof as a string.
140    fn display() -> String {
141        Self::display_offset(1)
142    }
143
144    /// Displays the proof as a string, with line numbers starting from `start_line_num`.
145    fn display_offset(start_line_num: usize) -> String;
146}
147
148/// Statically asserts that `Pr` is a proof of `P`, throwing a type checker error if it is not.
149///
150/// # Example
151///
152/// See the [module documentation](self).
153pub fn assert_proves<Pr, P>()
154where
155    Pr: Proof<Proves = P>,
156{
157}
158
159fn proof_display_formula<P>(line_num: usize) -> String
160where
161    P: Formula,
162{
163    format!("{:<3} {}", format!("{}.", line_num), P::display())
164}
165
166/// A formula that can be used in a proof without requiring it to have been deduced from previous steps.
167pub trait Axiom: Formula + Proof<Proves = Self> {}
168
169impl<A> Proof for A
170where
171    A: Axiom,
172{
173    type Proves = Self;
174
175    const LENGTH: usize = 1;
176
177    fn display_offset(start_line_num: usize) -> String {
178        proof_display_formula::<Self>(start_line_num)
179    }
180}
181
182/// The axiom schema `⊢ P -> (Q -> P)`, where `P, Q` are any formulas.
183#[allow(type_alias_bounds)]
184pub type Axiom1<P, Q>
185where
186    P: Formula,
187    Q: Formula,
188= Implies<P, Implies<Q, P>>;
189
190/// The axiom schema `⊢ (P -> (Q -> R)) -> ((P -> Q) -> (P -> R))`, where `P, Q, R` are any formulas.
191#[allow(type_alias_bounds)]
192pub type Axiom2<P, Q, R>
193where
194    P: Formula,
195    Q: Formula,
196    R: Formula,
197= Implies<Implies<P, Implies<Q, R>>, Implies<Implies<P, Q>, Implies<P, R>>>;
198
199/// The axiom schema `⊢ (¬Q -> ¬P) -> (P -> Q)`, where `P, Q` are any formulas.
200#[allow(type_alias_bounds)]
201pub type Axiom3<P, Q>
202where
203    P: Formula,
204    Q: Formula,
205= Implies<Implies<Not<Q>, Not<P>>, Implies<P, Q>>;
206
207impl<P, Q> Axiom for Axiom1<P, Q>
208where
209    P: Formula,
210    Q: Formula,
211{
212}
213
214impl<P, Q, R> Axiom for Axiom2<P, Q, R>
215where
216    P: Formula,
217    Q: Formula,
218    R: Formula,
219{
220}
221
222impl<P, Q> Axiom for Axiom3<P, Q>
223where
224    P: Formula,
225    Q: Formula,
226{
227}
228
229/// The modus ponens inference rule:
230/// if `⊢ P` and `⊢ (P -> Q)`, then `⊢ Q`.
231///
232/// `PrP` should be a proof of `P`. `PrI` should be a proof of `(P -> Q)`.
233pub struct MP<PrP, PrI> {
234    _p: PhantomData<PrP>,
235    _q: PhantomData<PrI>,
236}
237
238impl<P, Q, PrP, PrI> Proof for MP<PrP, PrI>
239where
240    P: Formula,
241    Q: Formula,
242    PrP: Proof<Proves = P>,
243    PrI: Proof<Proves = Implies<P, Q>>,
244{
245    type Proves = Q;
246
247    const LENGTH: usize = PrP::LENGTH + PrI::LENGTH + 1;
248
249    fn display_offset(start_line_num: usize) -> String {
250        format!(
251            "{}\n{}\n{}",
252            PrP::display_offset(start_line_num),
253            PrI::display_offset(start_line_num + PrP::LENGTH),
254            proof_display_formula::<Q>(start_line_num + PrP::LENGTH + PrI::LENGTH)
255        )
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use crate::formula::{P0, P2};
262
263    use super::*;
264
265    #[test]
266    #[allow(unused)]
267    fn prove_p_implies_p() {
268        type ToProve<P> = Implies<P, P>;
269
270        type Step1<P> = Axiom1<P, Implies<P0, P>>;
271        type Step2<P> = Axiom2<P, Implies<P0, P>, P>;
272        type Step3<P> = MP<Step1<P>, Step2<P>>;
273        type Step4<P> = Axiom1<P, P0>;
274        type Step5<P> = MP<Step4<P>, Step3<P>>;
275
276        fn for_all<P: Formula>() {
277            assert_proves::<Step5<P>, ToProve<P>>();
278        }
279    }
280
281    #[test]
282    fn display() {
283        // Proving p2 -> p2
284
285        type Step1 = Axiom1<P2, Implies<P0, P2>>;
286        type Step2 = Axiom2<P2, Implies<P0, P2>, P2>;
287        type Step3 = MP<Step1, Step2>;
288        type Step4 = Axiom1<P2, P0>;
289        type Step5 = MP<Step4, Step3>;
290
291        let expected_display = "1.  (p2 -> (p0 -> p2))
2922.  (p2 -> ((p0 -> p2) -> p2))
2933.  ((p2 -> ((p0 -> p2) -> p2)) -> ((p2 -> (p0 -> p2)) -> (p2 -> p2)))
2944.  ((p2 -> (p0 -> p2)) -> (p2 -> p2))
2955.  (p2 -> p2)";
296
297        assert_eq!(Step5::display(), expected_display);
298    }
299}