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}