type_proof/formula.rs
1//! Defines propositional formulas as types.
2//!
3//! # Language
4//!
5//! Our logical language consists of the the following symbols:
6//! - Propositional variables `p0, p1, p2, ...`
7//! - Logical negation `¬`
8//! - Logical implication `->`
9//! - Parentheses `( )`
10//!
11//! Note that `{¬, ->}` is a [functionally complete](https://en.wikipedia.org/wiki/Functional_completeness)
12//! set of Boolean operators, so other common operators (e.g. `∧, ∨`) can be expressed in terms of them.
13//!
14//! # Formulas
15//!
16//! A propositional formula is defined precisely using the following rules:
17//!
18//! - All propositional variables (type [`Var<N>`])
19//! - `¬P`, where `P` is a formula (type [`Not<P>`])
20//! - `(P -> Q)` where `P, Q` are formulas (type [`Implies<P, Q>`])
21//!
22//! The parentheses around `P -> Q` are to ensure unique readability / make operator precedence explicit. This
23//! also means that there's a simple bijection between the Rust types defined above and well-formed formulas in
24//! our language.
25//!
26//! There are some predefined type aliases for [`Var<N>`] for the first few natural numbers - for example, [`P0`]
27//! is an alias for [`Var<N0>`]. Propositional variables with arbitrarily large indices can be created using the
28//! [`P!`] macro.
29//!
30//! Some operators expressable in terms of `¬, ->` are defined via type aliases - for example, `∧` has the alias
31//! [`And<P, Q>`].
32//!
33//! ## Example
34//!
35//! The formula `(p0 -> ¬p2)` can be defined as a type like so:
36//!
37//! ```
38//! use type_proof::formula::{Implies, Not, P0, P2};
39//!
40//! type P = Implies<P0, Not<P2>>;
41//! ```
42//!
43//! # Valuations
44//!
45//! Given an assignment of [`True`] or [`False`] to each propositional variable in a formula, the formula can then
46//! be assigned a value of [`True`] or [`False`], using the following rules:
47//!
48//! ```text
49//! | P | ¬P |
50//! | ----- | ----- |
51//! | False | True |
52//! | True | False |
53//!
54//! | P | Q | (P -> Q) |
55//! | ----- | ----- | -------- |
56//! | False | False | True |
57//! | False | True | True |
58//! | True | False | False |
59//! | True | True | True |
60//! ```
61//!
62//! Valuations can be done via the [`Valuation`] trait.
63//!
64//! ## Example
65//!
66//! To find the value of `(p0 -> ¬p2)` under the assignment `p0 = True, p2 = False`:
67//!
68//! ```
69//! use type_proof::{
70//! boolean::{False, True},
71//! formula::{Implies, Not, P0, P2, Valuation},
72//! type_utils::assert_type_eq,
73//! };
74//!
75//! // The formula (p0 -> ¬p2)
76//! type P = Implies<P0, Not<P2>>;
77//!
78//! // Our valuation
79//! struct V;
80//! impl Valuation<P0> for V {
81//! type Value = True;
82//! }
83//! impl Valuation<P2> for V {
84//! type Value = False;
85//! }
86//!
87//! // V automatically implements Valuation<P> given Valuation<P0> and Valuation<P2>
88//! type ValueOfP = <V as Valuation<P>>::Value;
89//! assert_type_eq::<ValueOfP, True>();
90//! ``````
91//!
92//! [`True`]: crate::boolean::True
93//! [`False`]: crate::boolean::False
94
95use std::marker::PhantomData;
96
97use crate::{
98 boolean::{self, Bool},
99 peano::{N0, N1, N2, N3, N4, N5, N6, N7, N8, N9, N10, Nat},
100};
101
102pub use type_proof_macros::P;
103
104/// A propositional formula, encoded as a type.
105pub trait Formula {
106 /// Displays the formula as a string.
107 fn display() -> String;
108}
109
110/// A propositional variable.
111///
112/// `N` is the index of the propositional variable.
113pub struct Var<N>
114where
115 N: Nat,
116{
117 _n: PhantomData<N>,
118}
119
120impl<N> Formula for Var<N>
121where
122 N: Nat,
123{
124 fn display() -> String {
125 format!("p{}", N::VALUE)
126 }
127}
128
129/// The propositional variable `p0`.
130pub type P0 = Var<N0>;
131
132/// The propositional variable `p1`.
133pub type P1 = Var<N1>;
134
135/// The propositional variable `p2`.
136pub type P2 = Var<N2>;
137
138/// The propositional variable `p3`.
139pub type P3 = Var<N3>;
140
141/// The propositional variable `p4`.
142pub type P4 = Var<N4>;
143
144/// The propositional variable `p5`.
145pub type P5 = Var<N5>;
146
147/// The propositional variable `p6`.
148pub type P6 = Var<N6>;
149
150/// The propositional variable `p7`.
151pub type P7 = Var<N7>;
152
153/// The propositional variable `p8`.
154pub type P8 = Var<N8>;
155
156/// The propositional variable `p9`.
157pub type P9 = Var<N9>;
158
159/// The propositional variable `p10`.
160pub type P10 = Var<N10>;
161
162/// Logical negation:
163/// `¬P`
164pub struct Not<P>
165where
166 P: Formula,
167{
168 _p: PhantomData<P>,
169}
170
171impl<P> Formula for Not<P>
172where
173 P: Formula,
174{
175 fn display() -> String {
176 format!("¬{}", P::display())
177 }
178}
179
180/// Logical implication:
181/// `P -> Q`
182pub struct Implies<P, Q>
183where
184 P: Formula,
185 Q: Formula,
186{
187 _p: PhantomData<P>,
188 _q: PhantomData<Q>,
189}
190
191impl<P, Q> Formula for Implies<P, Q>
192where
193 P: Formula,
194 Q: Formula,
195{
196 fn display() -> String {
197 format!("({} -> {})", P::display(), Q::display())
198 }
199}
200
201/// Logical conjunction (and):
202/// `P ∧ Q`
203#[allow(type_alias_bounds)]
204pub type And<P, Q>
205where
206 P: Formula,
207 Q: Formula,
208= Not<Implies<P, Not<Q>>>;
209
210/// Logical disjunction (or):
211/// `P ∨ Q`
212#[allow(type_alias_bounds)]
213pub type Or<P, Q>
214where
215 P: Formula,
216 Q: Formula,
217= Not<And<Not<P>, Not<Q>>>; // using De Morgan's laws
218
219/// Logical equivalence:
220/// `P <-> Q`
221#[allow(type_alias_bounds)]
222pub type Iff<P, Q>
223where
224 P: Formula,
225 Q: Formula,
226= And<Implies<P, Q>, Implies<Q, P>>;
227
228/// Deduces the value of an expression given the value of each of its propositional variables.
229///
230/// A valuation type should implement [`Valuation`] for each of its propositional variables, and
231/// [`Valuation`] will automatically be implemented for all formulas constructible from those variables.
232///
233/// # Example
234///
235/// See the [module documentation](self).
236pub trait Valuation<P>
237where
238 P: Formula,
239{
240 /// The value of the valuation on `P`.
241 type Value: Bool;
242}
243
244impl<V, P> Valuation<Not<P>> for V
245where
246 P: Formula,
247 V: Valuation<P>,
248{
249 type Value = boolean::Not<<V as Valuation<P>>::Value>;
250}
251
252impl<V, P, Q> Valuation<Implies<P, Q>> for V
253where
254 P: Formula,
255 Q: Formula,
256 V: Valuation<P> + Valuation<Q>,
257{
258 type Value = boolean::Implies<<V as Valuation<P>>::Value, <V as Valuation<Q>>::Value>;
259}
260
261#[cfg(test)]
262mod tests {
263 use crate::{
264 boolean::{False, True},
265 type_utils::assert_type_eq,
266 };
267
268 use super::*;
269
270 #[test]
271 fn display() {
272 type F = Not<Implies<P0, Not<P2>>>;
273 assert_eq!(F::display(), "¬(p0 -> ¬p2)");
274 }
275
276 #[test]
277 fn valuation() {
278 // ¬(p0 -> ¬p2)
279 type P = Not<Implies<P0, Not<P2>>>;
280
281 struct V1;
282 impl Valuation<P0> for V1 {
283 type Value = True;
284 }
285 impl Valuation<P2> for V1 {
286 type Value = False;
287 }
288
289 type PVal1 = <V1 as Valuation<P>>::Value;
290 assert_type_eq::<PVal1, False>();
291
292 struct V2;
293 impl Valuation<P0> for V2 {
294 type Value = False;
295 }
296 impl Valuation<P2> for V2 {
297 type Value = True;
298 }
299
300 type PVal2 = <V2 as Valuation<P>>::Value;
301 assert_type_eq::<PVal2, False>();
302 }
303
304 #[test]
305 fn var_macro() {
306 assert_type_eq::<P!(0), P0>();
307 assert_type_eq::<P!(3), P3>();
308 }
309}