partial_functional/
semigroup.rs

1use std::{cmp::Ordering, marker::PhantomData};
2
3/// The trait combines two types into another one.
4///
5/// The combining of several types must be associative, meaning that they can be evaluated in any order.
6/// `first.combine(second.combine(third)) == first.combine(second).combine(third)`
7pub trait Semigroup {
8    fn combine(self, rhs: Self) -> Self;
9}
10
11/// This is just a small convienience macro to chain several combines together. Everthing after the first expression
12/// has to have a From<T> implementation for that type.
13///
14/// # Examples
15/// ```
16/// use partial_functional::prelude::*;
17/// use partial_functional::combine;
18///
19/// let min = combine!{ Min::empty(), 10, 30, 20 };
20/// assert_eq!(Min(10), min);
21/// ```
22#[macro_export]
23macro_rules! combine {
24    ( $init:expr , $($x:expr),+ $(,)? ) => {
25        $init$(
26            .combine($x.into())
27        )*
28    };
29}
30
31/// Returns the combination of both Some variants, if one is None then the other is returned.
32/// ```
33/// use partial_functional::semigroup::Semigroup;
34///
35/// let five = Some(5);
36/// let ten = Some(10);
37///
38/// assert_eq!(Some(15), five.combine(ten));
39/// assert_eq!(Some(5), five.combine(None));
40/// assert_eq!(Some(10), None.combine(ten));
41/// ```
42impl<T: Semigroup> Semigroup for Option<T> {
43    fn combine(self, rhs: Self) -> Self {
44        match (self, rhs) {
45            (Some(left), Some(right)) => Some(left.combine(right)),
46            (left, right) => left.or(right),
47        }
48    }
49}
50
51/// Returns the first Result if it's an Ok variant, otherwise returns the second
52///
53/// # Examples
54/// ```
55/// use partial_functional::semigroup::Semigroup;
56///
57/// let five: Result<u32, &'static str> = Ok(5);
58/// let two_kb: Result<u32, &'static str> = Ok(2048);
59/// let err: Result<u32, &'static str> = Err("An error occured");
60/// let err_again: Result<u32, &'static str> = Err("Another error");
61///
62/// assert_eq!(Ok(5), five.combine(err.clone()));
63/// assert_eq!(Ok(2048), two_kb.combine(five.clone()));
64/// assert_eq!(Ok(2048), two_kb.combine(five));
65/// assert_eq!(Err("Another error"), err.combine(err_again));
66/// ```
67impl<T, E> Semigroup for Result<T, E> {
68    fn combine(self, rhs: Self) -> Self {
69        match (self, rhs) {
70            (Err(_), b) => b,
71            (a, _) => a,
72        }
73    }
74}
75
76/// This combines two Ordering operations into one. 'first.cmp(second)' for example gives back an Ordering.
77/// If we have several types you wish to compare say name and age for example. We can first order by name and then age.
78///
79/// # Examples
80/// ```
81/// # use std::cmp::Ordering;
82/// use partial_functional::semigroup::Semigroup;
83///
84/// struct Person {
85///     name: String,
86///     age: u8,
87/// }
88///
89/// let first = Person { name: String::from("Chris"), age: 43 };
90/// let second = Person { name: String::from("Chris"), age: 23 };
91///
92/// let fst_compared_to_snd = first.name.cmp(&second.name).combine(first.age.cmp(&second.age));
93///
94/// assert_eq!(Ordering::Greater, fst_compared_to_snd);
95/// ```
96impl Semigroup for Ordering {
97    fn combine(self, rhs: Self) -> Self {
98        match (self, rhs) {
99            (Ordering::Less, _) => Ordering::Less,
100            (Ordering::Equal, y) => y,
101            (Ordering::Greater, _) => Ordering::Greater,
102        }
103    }
104}
105
106impl Semigroup for String {
107    fn combine(self, rhs: Self) -> Self {
108        self + &rhs
109    }
110}
111
112impl<T> Semigroup for Vec<T> {
113    fn combine(mut self, rhs: Self) -> Self {
114        self.extend(rhs);
115        self
116    }
117}
118macro_rules! impl_semigroup_with_addition {
119    ( $($x:ty),* ) => {
120        $(
121            impl Semigroup for $x {
122                fn combine(self, rhs: Self) -> Self {
123                    self + rhs
124                }
125            }
126        )*
127    };
128}
129
130impl_semigroup_with_addition!(
131    usize, isize, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64
132);
133
134impl<T> Semigroup for PhantomData<T> {
135    fn combine(self, _rhs: Self) -> Self {
136        self
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::monoid::{Last, Sum};
144    use quickcheck_macros::quickcheck;
145
146    #[quickcheck]
147    fn combine_option_is_the_sum_of_both(left: Option<u32>, right: Option<u32>) -> bool {
148        // cast everything to u64 so that we don't overflow when adding them up
149        let sum = match (left, right) {
150            (None, None) => None,
151            (None, Some(x)) => Some(x as u64),
152            (Some(x), None) => Some(x as u64),
153            (Some(x), Some(y)) => Some(x as u64 + y as u64),
154        };
155
156        let left = left.map(|x| x as u64);
157        let right = right.map(|x| x as u64);
158        let result = left.combine(right);
159
160        sum == result
161    }
162
163    #[quickcheck]
164    fn option_associativity_property(x: Option<u8>, y: Option<u8>, z: Option<u8>) -> bool {
165        let (x, y, z) = (
166            x.map(|x| x as u16),
167            y.map(|x| x as u16),
168            z.map(|x| x as u16),
169        );
170
171        x.combine(y.combine(z)) == x.combine(y).combine(z)
172    }
173
174    #[test]
175    fn option_combine_macro() {
176        let sum: Option<Sum<i32>> = crate::combine!(
177            None,
178            Sum::from(10),
179            None,
180            Sum::from(5),
181            Sum::from(7),
182            None,
183            Sum::from(42),
184            None,
185        );
186
187        assert_eq!(sum.unwrap(), 64);
188    }
189
190    #[test]
191    fn combine_macro() {
192        let x = crate::combine! {
193            Last::from(53), None, 42, {let b = None; b},
194        };
195
196        assert_eq!(x.0, Some(42));
197    }
198}