pub type Predicate<A> = Box<dyn Fn(&A) -> bool + Send + Sync>;
#[allow(clippy::module_inception)]
pub mod predicate {
use super::Predicate;
pub fn and<A: 'static>(p: Predicate<A>, q: Predicate<A>) -> Predicate<A> {
Box::new(move |a| p(a) && q(a))
}
pub fn or<A: 'static>(p: Predicate<A>, q: Predicate<A>) -> Predicate<A> {
Box::new(move |a| p(a) || q(a))
}
pub fn not<A: 'static>(p: Predicate<A>) -> Predicate<A> {
Box::new(move |a| !p(a))
}
pub fn xor<A: 'static>(p: Predicate<A>, q: Predicate<A>) -> Predicate<A> {
Box::new(move |a| p(a) ^ q(a))
}
pub fn implies<A: 'static>(p: Predicate<A>, q: Predicate<A>) -> Predicate<A> {
Box::new(move |a| !p(a) || q(a))
}
pub fn eqv<A: 'static>(p: Predicate<A>, q: Predicate<A>) -> Predicate<A> {
Box::new(move |a| p(a) == q(a))
}
pub fn contramap<A: 'static, B: 'static>(
p: Predicate<A>,
f: impl Fn(&B) -> A + Send + Sync + 'static,
) -> Predicate<B> {
Box::new(move |b| p(&f(b)))
}
pub fn product<A: 'static, B: 'static>(p: Predicate<A>, q: Predicate<B>) -> Predicate<(A, B)> {
Box::new(move |(a, b)| p(a) && q(b))
}
pub fn all<A: 'static>(predicates: Vec<Predicate<A>>) -> Predicate<Vec<A>> {
Box::new(move |values| predicates.iter().zip(values.iter()).all(|(p, v)| p(v)))
}
pub fn is_some<A: 'static>() -> Predicate<Option<A>> {
Box::new(|o| o.is_some())
}
pub fn is_none<A: 'static>() -> Predicate<Option<A>> {
Box::new(|o| o.is_none())
}
pub fn is_ok<A: 'static, E: 'static>() -> Predicate<Result<A, E>> {
Box::new(|r| r.is_ok())
}
pub fn is_err<A: 'static, E: 'static>() -> Predicate<Result<A, E>> {
Box::new(|r| r.is_err())
}
pub fn is_empty() -> Predicate<String> {
Box::new(|s: &String| s.is_empty())
}
pub fn is_non_empty() -> Predicate<String> {
Box::new(|s: &String| !s.is_empty())
}
pub fn str_is_empty() -> Predicate<str> {
Box::new(|s: &str| s.is_empty())
}
pub fn is_zero_i64() -> Predicate<i64> {
Box::new(|n| *n == 0)
}
pub fn is_positive_i64() -> Predicate<i64> {
Box::new(|n| *n > 0)
}
pub fn is_negative_i64() -> Predicate<i64> {
Box::new(|n| *n < 0)
}
}
#[cfg(test)]
mod tests {
use super::predicate;
use rstest::rstest;
fn positive() -> super::Predicate<i64> {
Box::new(|n: &i64| *n > 0)
}
fn even() -> super::Predicate<i64> {
Box::new(|n: &i64| *n % 2 == 0)
}
mod and {
use super::*;
#[rstest]
#[case::both_true(2_i64, true)] #[case::first_false(-2_i64, false)] #[case::second_false(3_i64, false)] #[case::both_false(-3_i64, false)] fn truth_table(#[case] input: i64, #[case] expected: bool) {
let p = predicate::and(positive(), even());
assert_eq!(p(&input), expected);
}
}
mod or {
use super::*;
#[rstest]
#[case::both_true(2_i64, true)]
#[case::first_only(3_i64, true)]
#[case::second_only(-2_i64, true)]
#[case::neither(-3_i64, false)]
fn truth_table(#[case] input: i64, #[case] expected: bool) {
let p = predicate::or(positive(), even());
assert_eq!(p(&input), expected);
}
}
mod not {
use super::*;
#[test]
fn not_positive_is_true_for_zero() {
let p = predicate::not(positive());
assert!(p(&0_i64));
}
#[test]
fn not_positive_is_true_for_negative() {
let p = predicate::not(positive());
assert!(p(&-5_i64));
}
#[test]
fn not_positive_is_false_for_positive() {
let p = predicate::not(positive());
assert!(!p(&1_i64));
}
}
mod xor {
use super::*;
#[rstest]
#[case::both_true(2_i64, false)]
#[case::first_only(3_i64, true)]
#[case::second_only(-2_i64, true)]
#[case::neither(-3_i64, false)]
fn truth_table(#[case] input: i64, #[case] expected: bool) {
let p = predicate::xor(positive(), even());
assert_eq!(p(&input), expected);
}
}
mod implies {
use super::*;
#[test]
fn true_implies_true_is_true() {
let p = predicate::implies(positive(), even());
assert!(p(&2_i64)); }
#[test]
fn true_implies_false_is_false() {
let p = predicate::implies(positive(), even());
assert!(!p(&3_i64)); }
#[test]
fn false_implies_anything_is_true() {
let p = predicate::implies(positive(), even());
assert!(p(&-3_i64)); assert!(p(&-2_i64)); }
}
mod eqv {
use super::*;
#[rstest]
#[case::both_true(2_i64, true)]
#[case::both_false(-3_i64, true)]
#[case::first_only(3_i64, false)]
#[case::second_only(-2_i64, false)]
fn truth_table(#[case] input: i64, #[case] expected: bool) {
let p = predicate::eqv(positive(), even());
assert_eq!(p(&input), expected);
}
}
mod contramap {
use super::*;
#[test]
fn test_string_by_length_positive() {
let p = predicate::contramap(positive(), |s: &String| s.len() as i64);
assert!(p(&"hello".to_string()));
}
#[test]
fn test_empty_string_length_not_positive() {
let p = predicate::contramap(positive(), |s: &String| s.len() as i64);
assert!(!p(&"".to_string()));
}
}
mod product {
use super::*;
#[test]
fn both_hold_returns_true() {
let p = predicate::product(positive(), even());
assert!(p(&(4_i64, 6_i64)));
}
#[test]
fn first_fails_returns_false() {
let p = predicate::product(positive(), even());
assert!(!p(&(-1_i64, 4_i64)));
}
#[test]
fn second_fails_returns_false() {
let p = predicate::product(positive(), even());
assert!(!p(&(3_i64, 3_i64)));
}
#[test]
fn both_fail_returns_false() {
let p = predicate::product(positive(), even());
assert!(!p(&(-1_i64, 3_i64)));
}
}
mod all {
use super::*;
#[test]
fn all_positive_values_returns_true() {
let ps = vec![positive(), positive(), positive()];
let p = predicate::all(ps);
assert!(p(&vec![1_i64, 2_i64, 3_i64]));
}
#[test]
fn one_failing_value_returns_false() {
let ps = vec![positive(), positive(), positive()];
let p = predicate::all(ps);
assert!(!p(&vec![1_i64, -1_i64, 3_i64]));
}
#[test]
fn empty_predicates_returns_true_for_any_vec() {
let ps: Vec<super::super::Predicate<i64>> = vec![];
let p = predicate::all(ps);
assert!(p(&vec![1_i64, 2_i64]));
}
}
mod builtins {
use super::*;
#[test]
fn is_some_true_for_some() {
assert!(predicate::is_some::<i32>()(&Some(1)));
}
#[test]
fn is_some_false_for_none() {
assert!(!predicate::is_some::<i32>()(&None));
}
#[test]
fn is_none_true_for_none() {
assert!(predicate::is_none::<i32>()(&None));
}
#[test]
fn is_none_false_for_some() {
assert!(!predicate::is_none::<i32>()(&Some(5)));
}
#[test]
fn is_ok_true_for_ok() {
let p = predicate::is_ok::<i32, &str>();
assert!(p(&Ok(1)));
}
#[test]
fn is_ok_false_for_err() {
let p = predicate::is_ok::<i32, &str>();
assert!(!p(&Err("oops")));
}
#[test]
fn is_err_true_for_err() {
let p = predicate::is_err::<i32, &str>();
assert!(p(&Err("bad")));
}
#[test]
fn is_err_false_for_ok() {
let p = predicate::is_err::<i32, &str>();
assert!(!p(&Ok(42)));
}
#[test]
fn is_empty_true_for_empty_string() {
assert!(predicate::is_empty()(&"".to_string()));
}
#[test]
fn is_empty_false_for_non_empty_string() {
assert!(!predicate::is_empty()(&"hi".to_string()));
}
#[test]
fn is_non_empty_true_for_non_empty() {
assert!(predicate::is_non_empty()(&"x".to_string()));
}
#[test]
fn is_non_empty_false_for_empty() {
assert!(!predicate::is_non_empty()(&"".to_string()));
}
#[rstest]
#[case::zero(0_i64, true)]
#[case::positive(1_i64, false)]
#[case::negative(-1_i64, false)]
fn is_zero_i64(#[case] input: i64, #[case] expected: bool) {
assert_eq!(predicate::is_zero_i64()(&input), expected);
}
#[rstest]
#[case::positive(5_i64, true)]
#[case::zero(0_i64, false)]
#[case::negative(-1_i64, false)]
fn is_positive_i64(#[case] input: i64, #[case] expected: bool) {
assert_eq!(predicate::is_positive_i64()(&input), expected);
}
#[rstest]
#[case::negative(-1_i64, true)]
#[case::zero(0_i64, false)]
#[case::positive(1_i64, false)]
fn is_negative_i64(#[case] input: i64, #[case] expected: bool) {
assert_eq!(predicate::is_negative_i64()(&input), expected);
}
}
}