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}