#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kleene {
True,
False,
Unknown(Inconclusive),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Inconclusive {
Absent,
Stale,
OpenWorldMiss,
}
impl Inconclusive {
pub fn names_a_fact(self) -> bool {
matches!(self, Inconclusive::Absent | Inconclusive::Stale)
}
}
impl Kleene {
pub const UNKNOWN: Kleene = Kleene::Unknown(Inconclusive::Absent);
pub fn from_bool(value: bool) -> Kleene {
if value {
Kleene::True
} else {
Kleene::False
}
}
pub fn is_known(self) -> bool {
!matches!(self, Kleene::Unknown(_))
}
pub fn reason(self) -> Option<Inconclusive> {
match self {
Kleene::Unknown(reason) => Some(reason),
_ => None,
}
}
pub fn negate(self) -> Kleene {
match self {
Kleene::True => Kleene::False,
Kleene::False => Kleene::True,
Kleene::Unknown(reason) => Kleene::Unknown(reason),
}
}
pub fn and(self, other: Kleene) -> Kleene {
match (self, other) {
(Kleene::False, _) | (_, Kleene::False) => Kleene::False,
(Kleene::True, Kleene::True) => Kleene::True,
(Kleene::Unknown(reason), _) => Kleene::Unknown(reason),
(Kleene::True, Kleene::Unknown(reason)) => Kleene::Unknown(reason),
}
}
pub fn or(self, other: Kleene) -> Kleene {
match (self, other) {
(Kleene::True, _) | (_, Kleene::True) => Kleene::True,
(Kleene::False, Kleene::False) => Kleene::False,
(Kleene::Unknown(reason), _) => Kleene::Unknown(reason),
(Kleene::False, Kleene::Unknown(reason)) => Kleene::Unknown(reason),
}
}
pub fn all(values: &[Kleene]) -> Kleene {
values
.iter()
.fold(Kleene::True, |left, right| left.and(*right))
}
pub fn any(values: &[Kleene]) -> Kleene {
values
.iter()
.fold(Kleene::False, |left, right| left.or(*right))
}
}
impl std::ops::Not for Kleene {
type Output = Kleene;
fn not(self) -> Kleene {
self.negate()
}
}
#[cfg(test)]
mod tests {
use super::*;
const STALE: Kleene = Kleene::Unknown(Inconclusive::Stale);
const MISS: Kleene = Kleene::Unknown(Inconclusive::OpenWorldMiss);
#[test]
fn the_negation_table_in_full() {
assert_eq!(Kleene::True.negate(), Kleene::False);
assert_eq!(Kleene::False.negate(), Kleene::True);
assert_eq!(Kleene::UNKNOWN.negate(), Kleene::UNKNOWN);
assert_eq!(STALE.negate(), STALE, "¬⊥ is ⊥ with the SAME reason");
assert_eq!(MISS.negate(), MISS);
}
#[test]
fn not_is_negate() {
for value in [Kleene::True, Kleene::False, Kleene::UNKNOWN, STALE, MISS] {
assert_eq!(!value, value.negate());
}
}
#[test]
fn the_conjunction_table_in_full() {
assert_eq!(Kleene::True.and(Kleene::True), Kleene::True);
assert_eq!(Kleene::True.and(Kleene::False), Kleene::False);
assert_eq!(Kleene::True.and(STALE), STALE);
assert_eq!(Kleene::False.and(Kleene::True), Kleene::False);
assert_eq!(Kleene::False.and(Kleene::False), Kleene::False);
assert_eq!(Kleene::False.and(STALE), Kleene::False);
assert_eq!(STALE.and(Kleene::True), STALE);
assert_eq!(STALE.and(Kleene::False), Kleene::False);
assert_eq!(STALE.and(MISS), STALE, "the LEFT reason survives");
}
#[test]
fn the_disjunction_table_in_full() {
assert_eq!(Kleene::True.or(Kleene::True), Kleene::True);
assert_eq!(Kleene::True.or(Kleene::False), Kleene::True);
assert_eq!(Kleene::True.or(STALE), Kleene::True);
assert_eq!(Kleene::False.or(Kleene::True), Kleene::True);
assert_eq!(Kleene::False.or(Kleene::False), Kleene::False);
assert_eq!(Kleene::False.or(STALE), STALE);
assert_eq!(STALE.or(Kleene::True), Kleene::True);
assert_eq!(STALE.or(Kleene::False), STALE);
assert_eq!(STALE.or(MISS), STALE, "the LEFT reason survives");
}
#[test]
fn both_connectives_are_commutative_in_truth_value() {
let values = [Kleene::True, Kleene::False, STALE, MISS];
for left in values {
for right in values {
let (a, b) = (left.and(right), right.and(left));
assert_eq!(a.is_known(), b.is_known());
if a.is_known() {
assert_eq!(a, b, "∧ commutes on decided values");
}
let (a, b) = (left.or(right), right.or(left));
assert_eq!(a.is_known(), b.is_known());
if a.is_known() {
assert_eq!(a, b, "∨ commutes on decided values");
}
}
}
}
#[test]
fn de_morgan_holds_across_all_three_values() {
let values = [Kleene::True, Kleene::False, STALE];
for left in values {
for right in values {
assert_eq!(!(left.and(right)), (!left).or(!right), "¬(p ∧ q) = ¬p ∨ ¬q");
assert_eq!(!(left.or(right)), (!left).and(!right), "¬(p ∨ q) = ¬p ∧ ¬q");
}
}
}
#[test]
fn the_excluded_middle_does_not_hold_and_that_is_the_point() {
assert_eq!(STALE.or(!STALE), STALE);
assert_eq!(STALE.and(!STALE), STALE);
}
#[test]
fn the_folds_carry_the_identities() {
assert_eq!(Kleene::all(&[]), Kleene::True, "empty ∧ is the identity");
assert_eq!(Kleene::any(&[]), Kleene::False, "empty ∨ is the identity");
assert_eq!(Kleene::all(&[Kleene::True, Kleene::True]), Kleene::True);
assert_eq!(Kleene::any(&[Kleene::False, Kleene::False]), Kleene::False);
}
#[test]
fn a_conjunction_does_not_short_circuit_past_a_later_bottom() {
let children = [Kleene::False, Kleene::True, STALE];
assert_eq!(Kleene::all(&children), Kleene::False);
assert!(
children.iter().any(|child| !child.is_known()),
"the caller can still see the ⊥ it must report — the slice is \
already evaluated, which is what the signature is for"
);
}
#[test]
fn a_disjunction_does_not_short_circuit_past_a_later_bottom() {
let children = [Kleene::True, MISS];
assert_eq!(Kleene::any(&children), Kleene::True);
assert!(children.iter().any(|child| !child.is_known()));
}
#[test]
fn the_first_bottom_in_a_fold_owns_the_reason() {
assert_eq!(Kleene::all(&[STALE, MISS]), STALE);
assert_eq!(Kleene::all(&[MISS, STALE]), MISS);
assert_eq!(Kleene::any(&[STALE, MISS]), STALE);
assert_eq!(Kleene::any(&[MISS, STALE]), MISS);
}
#[test]
fn only_an_unresolved_fact_belongs_in_inconclusive_facts() {
assert!(Inconclusive::Absent.names_a_fact());
assert!(Inconclusive::Stale.names_a_fact());
assert!(
!Inconclusive::OpenWorldMiss.names_a_fact(),
"the fact resolved and was read — what is unknown is whether a \
candidate is in a set that never claimed to be exhaustive"
);
}
#[test]
fn reason_hands_back_why_and_never_a_verdict() {
assert_eq!(Kleene::True.reason(), None);
assert_eq!(Kleene::False.reason(), None);
assert_eq!(STALE.reason(), Some(Inconclusive::Stale));
assert_eq!(MISS.reason(), Some(Inconclusive::OpenWorldMiss));
}
#[test]
fn from_bool_and_is_known_agree() {
assert_eq!(Kleene::from_bool(true), Kleene::True);
assert_eq!(Kleene::from_bool(false), Kleene::False);
assert!(Kleene::True.is_known());
assert!(Kleene::False.is_known());
assert!(!Kleene::UNKNOWN.is_known());
assert!(!MISS.is_known());
}
#[test]
fn no_code_path_in_this_module_yields_a_truth_value_default() {
let source = include_str!("kleene.rs");
let shipped = source
.split_once("#[cfg(test)]")
.map_or(source, |(before, _)| before);
let code: String = shipped
.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n");
for forbidden in [
"unwrap_or(false)",
"unwrap_or(true)",
"unwrap_or_default()",
"unwrap_or(Kleene",
"unwrap_or_else",
"impl Default for Kleene",
"derive(Default)",
"for bool",
] {
assert!(
!code.contains(forbidden),
"`{forbidden}` expresses a fact-level default, which D12 says \
must be impossible to write here"
);
}
}
}