1pub fn identity<A>(a: A) -> A {
14 a
15}
16
17pub fn const_<A: Clone>(value: A) -> impl Fn(()) -> A + Clone {
27 move |_| value.clone()
28}
29
30pub fn always<A: Clone, B>(value: A) -> impl Fn(B) -> A + Clone {
39 move |_| value.clone()
40}
41
42pub fn flip<A, B, C>(f: impl Fn(A, B) -> C) -> impl Fn(B, A) -> C {
51 move |b, a| f(a, b)
52}
53
54pub fn compose<A, B, C>(f: impl Fn(B) -> C, g: impl Fn(A) -> B) -> impl Fn(A) -> C {
64 move |a| f(g(a))
65}
66
67pub fn pipe1<A, B>(a: A, f: impl FnOnce(A) -> B) -> B {
76 f(a)
77}
78
79pub fn pipe2<A, B, C>(a: A, f: impl FnOnce(A) -> B, g: impl FnOnce(B) -> C) -> C {
81 g(f(a))
82}
83
84pub fn pipe3<A, B, C, D>(
86 a: A,
87 f: impl FnOnce(A) -> B,
88 g: impl FnOnce(B) -> C,
89 h: impl FnOnce(C) -> D,
90) -> D {
91 h(g(f(a)))
92}
93
94pub fn absurd<A>(never: std::convert::Infallible) -> A {
105 match never {}
106}
107
108pub fn tupled<A, B, C>(f: impl Fn(A, B) -> C) -> impl Fn((A, B)) -> C {
117 move |(a, b)| f(a, b)
118}
119
120pub fn untupled<A, B, C>(f: impl Fn((A, B)) -> C) -> impl Fn(A, B) -> C {
129 move |a, b| f((a, b))
130}
131
132pub fn memoize<A, B>(f: impl Fn(A) -> B) -> impl FnMut(A) -> B
141where
142 A: std::hash::Hash + Eq + Clone,
143 B: Clone,
144{
145 let mut cache = std::collections::HashMap::<A, B>::new();
146 move |a: A| {
147 if let Some(v) = cache.get(&a) {
148 return v.clone();
149 }
150 let v = f(a.clone());
151 cache.insert(a, v.clone());
152 v
153 }
154}
155
156#[cfg(test)]
159mod tests {
160 use super::*;
161 use rstest::rstest;
162
163 mod identity_tests {
166 use super::*;
167
168 #[rstest]
169 #[case::integer(42_i32)]
170 #[case::zero(0_i32)]
171 #[case::negative(-7_i32)]
172 fn identity_returns_input(#[case] v: i32) {
173 assert_eq!(identity(v), v);
174 }
175
176 #[test]
177 fn identity_works_for_strings() {
178 let s = "hello".to_string();
179 assert_eq!(identity(s.clone()), s);
180 }
181
182 #[test]
183 fn identity_works_for_options() {
184 assert_eq!(identity(Some(1_i32)), Some(1));
185 }
186 }
187
188 mod const_tests {
191 use super::*;
192
193 #[test]
194 fn const_always_returns_same_value() {
195 let f = const_(99_i32);
196 assert_eq!(f(()), 99);
197 assert_eq!(f(()), 99);
198 }
199
200 #[test]
201 fn always_ignores_argument() {
202 let f = always::<bool, i32>(true);
203 assert!(f(0));
204 assert!(f(42));
205 }
206
207 #[test]
208 fn always_with_string_value() {
209 let f = always::<&str, i32>("fixed");
210 assert_eq!(f(100), "fixed");
211 }
212 }
213
214 mod flip_tests {
217 use super::*;
218
219 #[test]
220 fn flip_reverses_arguments() {
221 let sub = |a: i32, b: i32| a - b;
222 let flipped = flip(sub);
223 assert_eq!(flipped(3, 10), 10 - 3);
224 }
225
226 #[test]
227 fn flip_of_flip_is_original() {
228 let f = |a: i32, b: i32| a * 10 + b;
229 let result = flip(flip(|a: i32, b: i32| a * 10 + b))(1, 2);
231 assert_eq!(result, f(1, 2));
232 }
233
234 #[rstest]
235 #[case(10_i32, 3_i32, -7_i32)]
236 #[case(5_i32, 5_i32, 0_i32)]
237 #[case(0_i32, 1_i32, 1_i32)]
238 fn flip_sub(#[case] b: i32, #[case] a: i32, #[case] expected: i32) {
239 let flipped_sub = flip(|a: i32, b: i32| a - b);
241 assert_eq!(flipped_sub(b, a), expected);
242 }
243 }
244
245 mod compose_tests {
248 use super::*;
249
250 #[test]
251 fn compose_applies_right_then_left() {
252 let add_one = |n: i32| n + 1;
253 let double = |n: i32| n * 2;
254 let composed = compose(add_one, double);
255 assert_eq!(composed(3), 7); }
257
258 #[test]
259 fn compose_with_identity_is_identity() {
260 let double = |n: i32| n * 2;
261 let composed = compose(identity, double);
262 assert_eq!(composed(5), 10);
263 }
264
265 #[rstest]
266 #[case(0_i32, 1_i32)]
267 #[case(1_i32, 3_i32)]
268 #[case(2_i32, 5_i32)]
269 fn compose_double_plus_one(#[case] input: i32, #[case] expected: i32) {
270 let f = compose(|n: i32| n + 1, |n: i32| n * 2);
271 assert_eq!(f(input), expected);
272 }
273 }
274
275 mod pipe_tests {
278 use super::*;
279
280 #[test]
281 fn pipe1_applies_single_function() {
282 assert_eq!(pipe1(5_i32, |n| n * 3), 15);
283 }
284
285 #[test]
286 fn pipe2_applies_two_functions_left_to_right() {
287 assert_eq!(pipe2(3_i32, |n| n * 2, |n| n + 1), 7);
288 }
289
290 #[test]
291 fn pipe3_applies_three_functions_left_to_right() {
292 assert_eq!(
293 pipe3(2_i32, |n| n + 1, |n| n * 2, |n| n - 1),
294 5 );
296 }
297
298 #[test]
299 fn pipe1_with_string_conversion() {
300 assert_eq!(pipe1(42_i32, |n| n.to_string()), "42");
301 }
302 }
303
304 mod tupled_tests {
310 use super::*;
311
312 #[test]
313 fn tupled_converts_two_arg_to_tuple_arg() {
314 let add = |a: i32, b: i32| a + b;
315 let tupled_add = tupled(add);
316 assert_eq!(tupled_add((3, 4)), 7);
317 }
318
319 #[test]
320 fn untupled_converts_tuple_arg_to_two_arg() {
321 let sum_pair = |(a, b): (i32, i32)| a + b;
322 let two_arg = untupled(sum_pair);
323 assert_eq!(two_arg(3, 4), 7);
324 }
325
326 #[test]
327 fn tupled_then_untupled_is_original() {
328 let f = |a: i32, b: i32| a - b;
329 let roundtrip = untupled(tupled(f));
330 assert_eq!(roundtrip(10, 3), 7);
331 }
332
333 #[rstest]
334 #[case(1_i32, 2_i32, 3_i32)]
335 #[case(0_i32, 0_i32, 0_i32)]
336 #[case(-1_i32, 1_i32, 0_i32)]
337 fn tupled_add_cases(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
338 let f = tupled(|a: i32, b: i32| a + b);
339 assert_eq!(f((a, b)), expected);
340 }
341 }
342
343 mod memoize_tests {
346 use super::*;
347
348 #[test]
349 fn memoize_returns_correct_value() {
350 let double = memoize(|n: i32| n * 2);
351 let mut double = double;
353 assert_eq!(double(5), 10);
354 }
355
356 #[test]
357 fn memoize_returns_same_value_on_second_call() {
358 let mut f = memoize(|n: i32| n + 100);
359 assert_eq!(f(3), 103);
360 assert_eq!(f(3), 103);
361 }
362
363 #[test]
364 fn memoize_caches_independently_per_key() {
365 let mut f = memoize(|s: &str| s.len());
366 assert_eq!(f("hi"), 2);
367 assert_eq!(f("hello"), 5);
368 assert_eq!(f("hi"), 2);
369 }
370 }
371}