fp_library/types/
option.rs

1//! Implementations for [`Option`].
2
3use crate::{
4	brands::OptionBrand,
5	classes::{
6		applicative::Applicative,
7		apply_first::ApplyFirst,
8		apply_second::ApplySecond,
9		clonable_fn::{ApplyClonableFn, ClonableFn},
10		foldable::Foldable,
11		functor::Functor,
12		lift::Lift,
13		monoid::Monoid,
14		pointed::Pointed,
15		semiapplicative::Semiapplicative,
16		semimonad::Semimonad,
17		traversable::Traversable,
18	},
19	hkt::{Apply1L1T, Kind1L1T},
20};
21
22impl Kind1L1T for OptionBrand {
23	type Output<'a, A: 'a> = Option<A>;
24}
25
26impl Functor for OptionBrand {
27	/// Maps a function over the value in the option.
28	///
29	/// # Type Signature
30	///
31	/// `forall a b. Functor Option => (a -> b, Option a) -> Option b`
32	///
33	/// # Parameters
34	///
35	/// * `f`: The function to apply to the value.
36	/// * `fa`: The option to map over.
37	///
38	/// # Returns
39	///
40	/// A new option containing the result of applying the function, or `None`.
41	///
42	/// # Examples
43	///
44	/// ```
45	/// use fp_library::classes::functor::map;
46	/// use fp_library::brands::OptionBrand;
47	///
48	/// assert_eq!(map::<OptionBrand, _, _, _>(|x: i32| x * 2, Some(5)), Some(10));
49	/// assert_eq!(map::<OptionBrand, _, _, _>(|x: i32| x * 2, None), None);
50	/// ```
51	fn map<'a, A: 'a, B: 'a, F>(
52		f: F,
53		fa: Apply1L1T<'a, Self, A>,
54	) -> Apply1L1T<'a, Self, B>
55	where
56		F: Fn(A) -> B + 'a,
57	{
58		fa.map(f)
59	}
60}
61
62impl Lift for OptionBrand {
63	/// Lifts a binary function into the option context.
64	///
65	/// # Type Signature
66	///
67	/// `forall a b c. Lift Option => ((a, b) -> c, Option a, Option b) -> Option c`
68	///
69	/// # Parameters
70	///
71	/// * `f`: The binary function to apply.
72	/// * `fa`: The first option.
73	/// * `fb`: The second option.
74	///
75	/// # Returns
76	///
77	/// `Some(f(a, b))` if both options are `Some`, otherwise `None`.
78	///
79	/// # Examples
80	///
81	/// ```
82	/// use fp_library::classes::lift::lift2;
83	/// use fp_library::brands::OptionBrand;
84	///
85	/// assert_eq!(lift2::<OptionBrand, _, _, _, _>(|x: i32, y: i32| x + y, Some(1), Some(2)), Some(3));
86	/// assert_eq!(lift2::<OptionBrand, _, _, _, _>(|x: i32, y: i32| x + y, Some(1), None), None);
87	/// ```
88	fn lift2<'a, A, B, C, F>(
89		f: F,
90		fa: Apply1L1T<'a, Self, A>,
91		fb: Apply1L1T<'a, Self, B>,
92	) -> Apply1L1T<'a, Self, C>
93	where
94		F: Fn(A, B) -> C + 'a,
95		A: 'a,
96		B: 'a,
97		C: 'a,
98	{
99		fa.zip(fb).map(|(a, b)| f(a, b))
100	}
101}
102
103impl Pointed for OptionBrand {
104	/// Wraps a value in an option.
105	///
106	/// # Type Signature
107	///
108	/// `forall a. Pointed Option => a -> Option a`
109	///
110	/// # Parameters
111	///
112	/// * `a`: The value to wrap.
113	///
114	/// # Returns
115	///
116	/// `Some(a)`.
117	///
118	/// # Examples
119	///
120	/// ```
121	/// use fp_library::classes::pointed::pure;
122	/// use fp_library::brands::OptionBrand;
123	///
124	/// assert_eq!(pure::<OptionBrand, _>(5), Some(5));
125	/// ```
126	fn pure<'a, A: 'a>(a: A) -> Apply1L1T<'a, Self, A> {
127		Some(a)
128	}
129}
130
131impl ApplyFirst for OptionBrand {}
132impl ApplySecond for OptionBrand {}
133
134impl Semiapplicative for OptionBrand {
135	/// Applies a wrapped function to a wrapped value.
136	///
137	/// # Type Signature
138	///
139	/// `forall a b. Semiapplicative Option => (Option (a -> b), Option a) -> Option b`
140	///
141	/// # Parameters
142	///
143	/// * `ff`: The option containing the function.
144	/// * `fa`: The option containing the value.
145	///
146	/// # Returns
147	///
148	/// `Some(f(a))` if both are `Some`, otherwise `None`.
149	///
150	/// # Examples
151	///
152	/// ```
153	/// use fp_library::classes::semiapplicative::apply;
154	/// use fp_library::classes::clonable_fn::ClonableFn;
155	/// use fp_library::brands::{OptionBrand};
156	/// use fp_library::brands::RcFnBrand;
157	/// use std::rc::Rc;
158	///
159	/// let f = Some(<RcFnBrand as ClonableFn>::new(|x: i32| x * 2));
160	/// assert_eq!(apply::<OptionBrand, _, _, RcFnBrand>(f, Some(5)), Some(10));
161	/// ```
162	fn apply<'a, A: 'a + Clone, B: 'a, FnBrand: 'a + ClonableFn>(
163		ff: Apply1L1T<'a, Self, ApplyClonableFn<'a, FnBrand, A, B>>,
164		fa: Apply1L1T<'a, Self, A>,
165	) -> Apply1L1T<'a, Self, B> {
166		match (ff, fa) {
167			(Some(f), Some(a)) => Some(f(a)),
168			_ => None,
169		}
170	}
171}
172
173impl Semimonad for OptionBrand {
174	/// Chains option computations.
175	///
176	/// # Type Signature
177	///
178	/// `forall a b. Semimonad Option => (Option a, a -> Option b) -> Option b`
179	///
180	/// # Parameters
181	///
182	/// * `ma`: The first option.
183	/// * `f`: The function to apply to the value inside the option.
184	///
185	/// # Returns
186	///
187	/// The result of applying `f` to the value if `ma` is `Some`, otherwise `None`.
188	///
189	/// # Examples
190	///
191	/// ```
192	/// use fp_library::classes::semimonad::bind;
193	/// use fp_library::brands::OptionBrand;
194	///
195	/// assert_eq!(bind::<OptionBrand, _, _, _>(Some(5), |x| Some(x * 2)), Some(10));
196	/// assert_eq!(bind::<OptionBrand, _, _, _>(None, |x: i32| Some(x * 2)), None);
197	/// ```
198	fn bind<'a, A: 'a, B: 'a, F>(
199		ma: Apply1L1T<'a, Self, A>,
200		f: F,
201	) -> Apply1L1T<'a, Self, B>
202	where
203		F: Fn(A) -> Apply1L1T<'a, Self, B> + 'a,
204	{
205		ma.and_then(f)
206	}
207}
208
209impl Foldable for OptionBrand {
210	/// Folds the option from the right.
211	///
212	/// # Type Signature
213	///
214	/// `forall a b. Foldable Option => ((a, b) -> b, b, Option a) -> b`
215	///
216	/// # Parameters
217	///
218	/// * `f`: The folding function.
219	/// * `init`: The initial value.
220	/// * `fa`: The option to fold.
221	///
222	/// # Returns
223	///
224	/// `f(a, init)` if `fa` is `Some(a)`, otherwise `init`.
225	///
226	/// # Examples
227	///
228	/// ```
229	/// use fp_library::classes::foldable::fold_right;
230	/// use fp_library::brands::OptionBrand;
231	///
232	/// assert_eq!(fold_right::<OptionBrand, _, _, _>(|x: i32, acc| x + acc, 0, Some(5)), 5);
233	/// assert_eq!(fold_right::<OptionBrand, _, _, _>(|x: i32, acc| x + acc, 0, None), 0);
234	/// ```
235	fn fold_right<'a, A: 'a, B: 'a, F>(
236		f: F,
237		init: B,
238		fa: Apply1L1T<'a, Self, A>,
239	) -> B
240	where
241		F: Fn(A, B) -> B + 'a,
242	{
243		match fa {
244			Some(a) => f(a, init),
245			None => init,
246		}
247	}
248
249	/// Folds the option from the left.
250	///
251	/// # Type Signature
252	///
253	/// `forall a b. Foldable Option => ((b, a) -> b, b, Option a) -> b`
254	///
255	/// # Parameters
256	///
257	/// * `f`: The folding function.
258	/// * `init`: The initial value.
259	/// * `fa`: The option to fold.
260	///
261	/// # Returns
262	///
263	/// `f(init, a)` if `fa` is `Some(a)`, otherwise `init`.
264	///
265	/// # Examples
266	///
267	/// ```
268	/// use fp_library::classes::foldable::fold_left;
269	/// use fp_library::brands::OptionBrand;
270	///
271	/// assert_eq!(fold_left::<OptionBrand, _, _, _>(|acc, x: i32| acc + x, 0, Some(5)), 5);
272	/// ```
273	fn fold_left<'a, A: 'a, B: 'a, F>(
274		f: F,
275		init: B,
276		fa: Apply1L1T<'a, Self, A>,
277	) -> B
278	where
279		F: Fn(B, A) -> B + 'a,
280	{
281		match fa {
282			Some(a) => f(init, a),
283			None => init,
284		}
285	}
286
287	/// Maps the value to a monoid and returns it, or returns empty.
288	///
289	/// # Type Signature
290	///
291	/// `forall a m. (Foldable Option, Monoid m) => ((a) -> m, Option a) -> m`
292	///
293	/// # Parameters
294	///
295	/// * `f`: The mapping function.
296	/// * `fa`: The option to fold.
297	///
298	/// # Returns
299	///
300	/// `f(a)` if `fa` is `Some(a)`, otherwise `M::empty()`.
301	///
302	/// # Examples
303	///
304	/// ```
305	/// use fp_library::classes::foldable::fold_map;
306	/// use fp_library::brands::OptionBrand;
307	/// use fp_library::types::string; // Import to bring Monoid impl for String into scope
308	///
309	/// assert_eq!(fold_map::<OptionBrand, _, _, _>(|x: i32| x.to_string(), Some(5)), "5".to_string());
310	/// ```
311	fn fold_map<'a, A: 'a, M, F>(
312		f: F,
313		fa: Apply1L1T<'a, Self, A>,
314	) -> M
315	where
316		M: Monoid + 'a,
317		F: Fn(A) -> M + 'a,
318	{
319		match fa {
320			Some(a) => f(a),
321			None => M::empty(),
322		}
323	}
324}
325
326impl Traversable for OptionBrand {
327	/// Traverses the option with an applicative function.
328	///
329	/// # Type Signature
330	///
331	/// `forall a b f. (Traversable Option, Applicative f) => (a -> f b, Option a) -> f (Option b)`
332	///
333	/// # Parameters
334	///
335	/// * `f`: The function to apply.
336	/// * `ta`: The option to traverse.
337	///
338	/// # Returns
339	///
340	/// The option wrapped in the applicative context.
341	///
342	/// # Examples
343	///
344	/// ```
345	/// use fp_library::classes::traversable::traverse;
346	/// use fp_library::brands::OptionBrand;
347	///
348	/// assert_eq!(traverse::<OptionBrand, OptionBrand, _, _, _>(|x| Some(x * 2), Some(5)), Some(Some(10)));
349	/// ```
350	fn traverse<'a, F: Applicative, A: 'a + Clone, B: 'a + Clone, Func>(
351		f: Func,
352		ta: Apply1L1T<'a, Self, A>,
353	) -> Apply1L1T<'a, F, Apply1L1T<'a, Self, B>>
354	where
355		Func: Fn(A) -> Apply1L1T<'a, F, B> + 'a,
356		Apply1L1T<'a, Self, B>: Clone,
357	{
358		match ta {
359			Some(a) => F::map(|b| Some(b), f(a)),
360			None => F::pure(None),
361		}
362	}
363
364	/// Sequences an option of applicative.
365	///
366	/// # Type Signature
367	///
368	/// `forall a f. (Traversable Option, Applicative f) => (Option (f a)) -> f (Option a)`
369	///
370	/// # Parameters
371	///
372	/// * `ta`: The option containing the applicative value.
373	///
374	/// # Returns
375	///
376	/// The option wrapped in the applicative context.
377	///
378	/// # Examples
379	///
380	/// ```
381	/// use fp_library::classes::traversable::sequence;
382	/// use fp_library::brands::OptionBrand;
383	///
384	/// assert_eq!(sequence::<OptionBrand, OptionBrand, _>(Some(Some(5))), Some(Some(5)));
385	/// ```
386	fn sequence<'a, F: Applicative, A: 'a + Clone>(
387		ta: Apply1L1T<'a, Self, Apply1L1T<'a, F, A>>
388	) -> Apply1L1T<'a, F, Apply1L1T<'a, Self, A>>
389	where
390		Apply1L1T<'a, F, A>: Clone,
391		Apply1L1T<'a, Self, A>: Clone,
392	{
393		match ta {
394			Some(fa) => F::map(|a| Some(a), fa),
395			None => F::pure(None),
396		}
397	}
398}
399
400#[cfg(test)]
401mod tests {
402	use super::*;
403	use crate::{
404		brands::RcFnBrand,
405		classes::{functor::map, pointed::pure, semiapplicative::apply, semimonad::bind},
406		functions::{compose, identity},
407	};
408	use quickcheck_macros::quickcheck;
409
410	// Functor Laws
411
412	/// Tests the identity law for Functor.
413	#[quickcheck]
414	fn functor_identity(x: Option<i32>) -> bool {
415		map::<OptionBrand, _, _, _>(identity, x) == x
416	}
417
418	/// Tests the composition law for Functor.
419	#[quickcheck]
420	fn functor_composition(x: Option<i32>) -> bool {
421		let f = |x: i32| x.wrapping_add(1);
422		let g = |x: i32| x.wrapping_mul(2);
423		map::<OptionBrand, _, _, _>(compose(f, g), x)
424			== map::<OptionBrand, _, _, _>(f, map::<OptionBrand, _, _, _>(g, x))
425	}
426
427	// Applicative Laws
428
429	/// Tests the identity law for Applicative.
430	#[quickcheck]
431	fn applicative_identity(v: Option<i32>) -> bool {
432		apply::<OptionBrand, _, _, RcFnBrand>(
433			pure::<OptionBrand, _>(<RcFnBrand as ClonableFn>::new(identity)),
434			v,
435		) == v
436	}
437
438	/// Tests the homomorphism law for Applicative.
439	#[quickcheck]
440	fn applicative_homomorphism(x: i32) -> bool {
441		let f = |x: i32| x.wrapping_mul(2);
442		apply::<OptionBrand, _, _, RcFnBrand>(
443			pure::<OptionBrand, _>(<RcFnBrand as ClonableFn>::new(f)),
444			pure::<OptionBrand, _>(x),
445		) == pure::<OptionBrand, _>(f(x))
446	}
447
448	/// Tests the composition law for Applicative.
449	#[quickcheck]
450	fn applicative_composition(
451		w: Option<i32>,
452		u_is_some: bool,
453		v_is_some: bool,
454	) -> bool {
455		let v_fn = |x: i32| x.wrapping_mul(2);
456		let u_fn = |x: i32| x.wrapping_add(1);
457
458		let v = if v_is_some {
459			pure::<OptionBrand, _>(<RcFnBrand as ClonableFn>::new(v_fn))
460		} else {
461			None
462		};
463		let u = if u_is_some {
464			pure::<OptionBrand, _>(<RcFnBrand as ClonableFn>::new(u_fn))
465		} else {
466			None
467		};
468
469		// RHS: u <*> (v <*> w)
470		let vw = apply::<OptionBrand, _, _, RcFnBrand>(v.clone(), w.clone());
471		let rhs = apply::<OptionBrand, _, _, RcFnBrand>(u.clone(), vw);
472
473		// LHS: pure(compose) <*> u <*> v <*> w
474		// equivalent to (u . v) <*> w
475		let uv = match (u, v) {
476			(Some(uf), Some(vf)) => {
477				let composed = move |x| uf(vf(x));
478				Some(<RcFnBrand as ClonableFn>::new(composed))
479			}
480			_ => None,
481		};
482
483		let lhs = apply::<OptionBrand, _, _, RcFnBrand>(uv, w);
484
485		lhs == rhs
486	}
487
488	/// Tests the interchange law for Applicative.
489	#[quickcheck]
490	fn applicative_interchange(y: i32) -> bool {
491		// u <*> pure y = pure ($ y) <*> u
492		let f = |x: i32| x.wrapping_mul(2);
493		let u = pure::<OptionBrand, _>(<RcFnBrand as ClonableFn>::new(f));
494
495		let lhs = apply::<OptionBrand, _, _, RcFnBrand>(u.clone(), pure::<OptionBrand, _>(y));
496
497		let rhs_fn = <RcFnBrand as ClonableFn>::new(move |f: std::rc::Rc<dyn Fn(i32) -> i32>| f(y));
498		let rhs = apply::<OptionBrand, _, _, RcFnBrand>(pure::<OptionBrand, _>(rhs_fn), u);
499
500		lhs == rhs
501	}
502
503	// Monad Laws
504
505	/// Tests the left identity law for Monad.
506	#[quickcheck]
507	fn monad_left_identity(a: i32) -> bool {
508		let f = |x: i32| Some(x.wrapping_mul(2));
509		bind::<OptionBrand, _, _, _>(pure::<OptionBrand, _>(a), f) == f(a)
510	}
511
512	/// Tests the right identity law for Monad.
513	#[quickcheck]
514	fn monad_right_identity(m: Option<i32>) -> bool {
515		bind::<OptionBrand, _, _, _>(m, pure::<OptionBrand, _>) == m
516	}
517
518	/// Tests the associativity law for Monad.
519	#[quickcheck]
520	fn monad_associativity(m: Option<i32>) -> bool {
521		let f = |x: i32| Some(x.wrapping_mul(2));
522		let g = |x: i32| Some(x.wrapping_add(1));
523		bind::<OptionBrand, _, _, _>(bind::<OptionBrand, _, _, _>(m, f), g)
524			== bind::<OptionBrand, _, _, _>(m, |x| bind::<OptionBrand, _, _, _>(f(x), g))
525	}
526
527	// Edge Cases
528
529	/// Tests `map` on `None`.
530	#[test]
531	fn map_none() {
532		assert_eq!(map::<OptionBrand, _, _, _>(|x: i32| x + 1, None), None);
533	}
534
535	/// Tests `bind` on `None`.
536	#[test]
537	fn bind_none() {
538		assert_eq!(bind::<OptionBrand, _, _, _>(None, |x: i32| Some(x + 1)), None);
539	}
540
541	/// Tests `bind` returning `None`.
542	#[test]
543	fn bind_returning_none() {
544		assert_eq!(bind::<OptionBrand, _, _, _>(Some(5), |_| None::<i32>), None);
545	}
546
547	/// Tests `fold_right` on `None`.
548	#[test]
549	fn fold_right_none() {
550		assert_eq!(
551			crate::classes::foldable::fold_right::<OptionBrand, _, _, _>(
552				|x: i32, acc| x + acc,
553				0,
554				None
555			),
556			0
557		);
558	}
559
560	/// Tests `fold_left` on `None`.
561	#[test]
562	fn fold_left_none() {
563		assert_eq!(
564			crate::classes::foldable::fold_left::<OptionBrand, _, _, _>(
565				|acc, x: i32| acc + x,
566				0,
567				None
568			),
569			0
570		);
571	}
572
573	/// Tests `traverse` on `None`.
574	#[test]
575	fn traverse_none() {
576		assert_eq!(
577			crate::classes::traversable::traverse::<OptionBrand, OptionBrand, _, _, _>(
578				|x: i32| Some(x + 1),
579				None
580			),
581			Some(None)
582		);
583	}
584
585	/// Tests `traverse` returning `None`.
586	#[test]
587	fn traverse_returning_none() {
588		assert_eq!(
589			crate::classes::traversable::traverse::<OptionBrand, OptionBrand, _, _, _>(
590				|_: i32| None::<i32>,
591				Some(5)
592			),
593			None
594		);
595	}
596}