#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Connective {
And,
Or,
Not,
Implies,
Iff,
Xor,
Nand,
Nor,
}
impl Connective {
pub fn variants() -> Vec<Self> {
vec![
Connective::And,
Connective::Or,
Connective::Not,
Connective::Implies,
Connective::Iff,
Connective::Xor,
Connective::Nand,
Connective::Nor,
]
}
pub fn eval(&self, a: bool, b: bool) -> bool {
match self {
Connective::And => a && b,
Connective::Or => a || b,
Connective::Not => !a, Connective::Implies => !a || b,
Connective::Iff => a == b,
Connective::Xor => a ^ b,
Connective::Nand => !(a && b),
Connective::Nor => !(a || b),
}
}
pub fn is_commutative(&self) -> bool {
matches!(
self,
Connective::And
| Connective::Or
| Connective::Iff
| Connective::Xor
| Connective::Nand
| Connective::Nor
)
}
pub fn arity(&self) -> u8 {
match self {
Connective::Not => 1,
_ => 2,
}
}
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn is_tautology(f: impl Fn(bool, bool) -> bool) -> bool {
for &a in &[true, false] {
for &b in &[true, false] {
if !f(a, b) {
return false;
}
}
}
true
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn de_morgan_and() -> bool {
is_tautology(|a, b| !(a && b) == (!a || !b))
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn de_morgan_or() -> bool {
is_tautology(|a, b| !(a || b) == (!a && !b))
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn double_negation() -> bool {
[true, false].iter().all(|&a| !!a == a)
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn modus_ponens() -> bool {
is_tautology(|a, b| {
let premise = a && (!a || b); !premise || b })
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn contrapositive() -> bool {
is_tautology(|a, b| {
let forward = !a || b; let contra = b || !a; forward == contra
})
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn excluded_middle() -> bool {
[true, false].iter().all(|&a| a || !a)
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn non_contradiction() -> bool {
[true, false].iter().all(|&a| !(a && !a))
}
#[allow(clippy::nonminimal_bool, clippy::overly_complex_bool_expr)]
pub fn nand_is_universal() -> bool {
let nand = |a: bool, b: bool| !(a && b);
let not_via_nand = |a: bool| nand(a, a);
let and_via_nand = |a: bool, b: bool| nand(nand(a, b), nand(a, b));
let or_via_nand = |a: bool, b: bool| nand(nand(a, a), nand(b, b));
is_tautology(|a, _b| not_via_nand(a) == !a)
&& is_tautology(|a, b| and_via_nand(a, b) == (a && b))
&& is_tautology(|a, b| or_via_nand(a, b) == (a || b))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_8_connectives() {
assert_eq!(Connective::variants().len(), 8);
}
#[test]
fn test_commutative_connectives() {
let count = Connective::variants()
.iter()
.filter(|c| c.is_commutative())
.count();
assert_eq!(count, 6); }
#[test]
fn test_de_morgan_and() {
assert!(de_morgan_and());
}
#[test]
fn test_de_morgan_or() {
assert!(de_morgan_or());
}
#[test]
fn test_double_negation() {
assert!(double_negation());
}
#[test]
fn test_modus_ponens() {
assert!(modus_ponens());
}
#[test]
fn test_contrapositive() {
assert!(contrapositive());
}
#[test]
fn test_excluded_middle() {
assert!(excluded_middle());
}
#[test]
fn test_non_contradiction() {
assert!(non_contradiction());
}
#[test]
fn test_nand_universal() {
assert!(nand_is_universal());
}
proptest! {
#[test]
fn prop_commutative(a in proptest::bool::ANY, b in proptest::bool::ANY) {
for c in Connective::variants() {
if c.is_commutative() {
prop_assert_eq!(c.eval(a, b), c.eval(b, a), "{:?} not commutative", c);
}
}
}
#[test]
fn prop_implies_not_commutative(a in proptest::bool::ANY, b in proptest::bool::ANY) {
if a != b {
prop_assert_ne!(Connective::Implies.eval(a, b), Connective::Implies.eval(b, a));
}
}
#[test]
fn prop_and_identity(a in proptest::bool::ANY) {
prop_assert_eq!(Connective::And.eval(a, true), a);
}
#[test]
fn prop_or_identity(a in proptest::bool::ANY) {
prop_assert_eq!(Connective::Or.eval(a, false), a);
}
#[test]
fn prop_xor_self_false(a in proptest::bool::ANY) {
prop_assert_eq!(Connective::Xor.eval(a, a), false);
}
#[test]
fn prop_iff_reflexive(a in proptest::bool::ANY) {
prop_assert_eq!(Connective::Iff.eval(a, a), true);
}
#[test]
fn prop_nand_is_not_and(a in proptest::bool::ANY, b in proptest::bool::ANY) {
prop_assert_eq!(Connective::Nand.eval(a, b), !Connective::And.eval(a, b));
}
#[test]
fn prop_nor_is_not_or(a in proptest::bool::ANY, b in proptest::bool::ANY) {
prop_assert_eq!(Connective::Nor.eval(a, b), !Connective::Or.eval(a, b));
}
}
}