Skip to main content

fp_library/classes/
extend.rs

1//! Cooperative extension of a local context-dependent computation to a global computation.
2//!
3//! [`Extend`] is the dual of [`Semimonad`](crate::classes::Semimonad): where `bind` sequences
4//! computations by extracting a value and feeding it to a function that produces a new context,
5//! `extend` takes a function that consumes a whole context and lifts it to operate within
6//! the context. In categorical terms, `extend` is co-Kleisli extension.
7//!
8//! This module is a port of PureScript's
9//! [`Control.Extend`](https://pursuit.purescript.org/packages/purescript-control/docs/Control.Extend).
10
11#[fp_macros::document_module]
12mod inner {
13	use {
14		crate::{
15			classes::*,
16			kinds::*,
17		},
18		fp_macros::*,
19	};
20
21	/// A type class for types that support co-Kleisli extension.
22	///
23	/// `Extend` is the dual of [`Semimonad`](crate::classes::Semimonad). Where
24	/// `bind : (A -> F<B>) -> F<A> -> F<B>` feeds a single extracted value into a
25	/// function that produces a new context, `extend : (F<A> -> B) -> F<A> -> F<B>`
26	/// feeds an entire context into a function and re-wraps the result.
27	///
28	/// `class Functor w <= Extend w`
29	///
30	/// # Laws
31	///
32	/// **Associativity:** composing two extensions is the same as extending with
33	/// a pre-composed function.
34	///
35	/// For any `f: F<B> -> C` and `g: F<A> -> B`:
36	///
37	/// ```text
38	/// extend(f, extend(g, w)) == extend(|w| f(extend(g, w)), w)
39	/// ```
40	///
41	/// This is dual to the associativity law for `bind`.
42	///
43	/// # Note on `LazyBrand`
44	///
45	/// `LazyBrand` cannot implement `Extend` because `Extend: Functor` and
46	/// `LazyBrand` cannot implement `Functor` (its `evaluate` returns `&A`,
47	/// not owned `A`). PureScript's `Lazy` has `Extend`/`Comonad` because GC
48	/// provides owned values from `force`. In this library, `ThunkBrand` fills
49	/// the `Functor + Extend + Comonad` role for lazy types, while `LazyBrand`
50	/// provides memoization via [`RefFunctor`](crate::classes::RefFunctor).
51	#[document_examples]
52	///
53	/// Associativity law for [`Vec`]:
54	///
55	/// ```
56	/// use fp_library::{
57	/// 	brands::*,
58	/// 	functions::*,
59	/// };
60	///
61	/// let w = vec![1, 2, 3];
62	/// let f = |v: Vec<i32>| v.iter().sum::<i32>();
63	/// let g = |v: Vec<i32>| v.len() as i32;
64	///
65	/// // extend(f, extend(g, w)) == extend(|w| f(extend(g, w)), w)
66	/// let lhs = extend::<VecBrand, _, _>(f, extend::<VecBrand, _, _>(g, w.clone()));
67	/// let rhs = extend::<VecBrand, _, _>(|w| f(extend::<VecBrand, _, _>(g, w)), w);
68	/// assert_eq!(lhs, rhs);
69	/// ```
70	pub trait Extend: Functor {
71		/// Extends a local context-dependent computation to a global computation.
72		///
73		/// Given a function that consumes an `F<A>` and produces a `B`, and a
74		/// value of type `F<A>`, produces an `F<B>` by applying the function in
75		/// a context-sensitive way.
76		#[document_signature]
77		///
78		#[document_type_parameters(
79			"The lifetime of the values.",
80			"The type of the value(s) inside the comonadic context.",
81			"The result type of the extension function."
82		)]
83		///
84		#[document_parameters(
85			"The function that consumes a whole context and produces a value.",
86			"The comonadic context to extend over."
87		)]
88		///
89		#[document_returns(
90			"A new comonadic context containing the results of applying the function."
91		)]
92		#[document_examples]
93		///
94		/// ```
95		/// use fp_library::{
96		/// 	brands::*,
97		/// 	classes::*,
98		/// 	types::*,
99		/// };
100		///
101		/// let result = IdentityBrand::extend(|id: Identity<i32>| id.0 * 2, Identity(5));
102		/// assert_eq!(result, Identity(10));
103		/// ```
104		fn extend<'a, A: 'a + Clone, B: 'a>(
105			f: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
106			wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
107		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
108
109		/// Duplicates a comonadic context, wrapping it inside another layer of the same context.
110		///
111		/// `duplicate(wa)` is equivalent to `extend(identity, wa)`. It is the dual of
112		/// [`join`](crate::functions::join) for monads.
113		///
114		/// Produces `F<F<A>>` from `F<A>`, embedding the original context as the inner value.
115		#[document_signature]
116		///
117		#[document_type_parameters(
118			"The lifetime of the values.",
119			"The type of the value(s) inside the comonadic context."
120		)]
121		///
122		#[document_parameters("The comonadic context to duplicate.")]
123		///
124		#[document_returns("A doubly-wrapped comonadic context.")]
125		#[document_examples]
126		///
127		/// ```
128		/// use fp_library::{
129		/// 	brands::*,
130		/// 	classes::*,
131		/// 	types::*,
132		/// };
133		///
134		/// let result = IdentityBrand::duplicate(Identity(5));
135		/// assert_eq!(result, Identity(Identity(5)));
136		/// ```
137		fn duplicate<'a, A: 'a + Clone>(
138			wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
139		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)>)
140		where
141			Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): 'a, {
142			Self::extend(|w| w, wa)
143		}
144
145		/// Extends with the arguments flipped.
146		///
147		/// A version of [`extend`](Extend::extend) where the comonadic context comes
148		/// first, followed by the extension function.
149		#[document_signature]
150		///
151		#[document_type_parameters(
152			"The lifetime of the values.",
153			"The type of the value(s) inside the comonadic context.",
154			"The result type of the extension function."
155		)]
156		///
157		#[document_parameters(
158			"The comonadic context to extend over.",
159			"The function that consumes a whole context and produces a value."
160		)]
161		///
162		#[document_returns(
163			"A new comonadic context containing the results of applying the function."
164		)]
165		#[document_examples]
166		///
167		/// ```
168		/// use fp_library::{
169		/// 	brands::*,
170		/// 	classes::*,
171		/// 	types::*,
172		/// };
173		///
174		/// let result = IdentityBrand::extend_flipped(Identity(5), |id| id.0 * 3);
175		/// assert_eq!(result, Identity(15));
176		/// ```
177		fn extend_flipped<'a, A: 'a + Clone, B: 'a>(
178			wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
179			f: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
180		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
181			Self::extend(f, wa)
182		}
183
184		/// Forwards co-Kleisli composition.
185		///
186		/// Composes two co-Kleisli functions left-to-right: first applies `f` via
187		/// [`extend`](Extend::extend), then applies `g` to the result. This is the
188		/// dual of [`compose_kleisli`](crate::functions::compose_kleisli).
189		#[document_signature]
190		///
191		#[document_type_parameters(
192			"The lifetime of the values.",
193			"The type of the value(s) inside the comonadic context.",
194			"The result type of the first co-Kleisli function.",
195			"The result type of the second co-Kleisli function."
196		)]
197		///
198		#[document_parameters(
199			"The first co-Kleisli function.",
200			"The second co-Kleisli function.",
201			"The comonadic context to operate on."
202		)]
203		///
204		#[document_returns(
205			"The result of composing both co-Kleisli functions and applying them to the context."
206		)]
207		#[document_examples]
208		///
209		/// ```
210		/// use fp_library::{
211		/// 	brands::*,
212		/// 	classes::*,
213		/// 	types::*,
214		/// };
215		///
216		/// let f = |id: Identity<i32>| id.0 + 1;
217		/// let g = |id: Identity<i32>| id.0 * 10;
218		/// let result = IdentityBrand::compose_co_kleisli(f, g, Identity(5));
219		/// assert_eq!(result, 60);
220		/// ```
221		fn compose_co_kleisli<'a, A: 'a + Clone, B: 'a + Clone, C: 'a>(
222			f: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
223			g: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)) -> C + 'a,
224			wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
225		) -> C {
226			g(Self::extend(f, wa))
227		}
228
229		/// Backwards co-Kleisli composition.
230		///
231		/// Composes two co-Kleisli functions right-to-left: first applies `g` via
232		/// [`extend`](Extend::extend), then applies `f` to the result. This is the
233		/// dual of [`compose_kleisli_flipped`](crate::functions::compose_kleisli_flipped).
234		#[document_signature]
235		///
236		#[document_type_parameters(
237			"The lifetime of the values.",
238			"The type of the value(s) inside the comonadic context.",
239			"The result type of the second co-Kleisli function (applied first).",
240			"The result type of the first co-Kleisli function (applied second)."
241		)]
242		///
243		#[document_parameters(
244			"The second co-Kleisli function (applied after `g`).",
245			"The first co-Kleisli function (applied first to the context).",
246			"The comonadic context to operate on."
247		)]
248		///
249		#[document_returns(
250			"The result of composing both co-Kleisli functions and applying them to the context."
251		)]
252		#[document_examples]
253		///
254		/// ```
255		/// use fp_library::{
256		/// 	brands::*,
257		/// 	classes::*,
258		/// 	types::*,
259		/// };
260		///
261		/// let f = |id: Identity<i32>| id.0 * 10;
262		/// let g = |id: Identity<i32>| id.0 + 1;
263		/// let result = IdentityBrand::compose_co_kleisli_flipped(f, g, Identity(5));
264		/// assert_eq!(result, 60);
265		/// ```
266		fn compose_co_kleisli_flipped<'a, A: 'a + Clone, B: 'a + Clone, C: 'a>(
267			f: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)) -> C + 'a,
268			g: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
269			wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
270		) -> C {
271			f(Self::extend(g, wa))
272		}
273	}
274
275	/// Extends a local context-dependent computation to a global computation.
276	///
277	/// Free function version that dispatches to [the type class' associated function][`Extend::extend`].
278	#[document_signature]
279	///
280	#[document_type_parameters(
281		"The lifetime of the values.",
282		"The brand of the comonadic context.",
283		"The type of the value(s) inside the comonadic context.",
284		"The result type of the extension function."
285	)]
286	///
287	#[document_parameters(
288		"The function that consumes a whole context and produces a value.",
289		"The comonadic context to extend over."
290	)]
291	///
292	#[document_returns("A new comonadic context containing the results of applying the function.")]
293	#[document_examples]
294	///
295	/// ```
296	/// use fp_library::{
297	/// 	brands::*,
298	/// 	functions::*,
299	/// 	types::*,
300	/// };
301	///
302	/// // extend on Identity: apply f to the whole container
303	/// let w = Identity(10);
304	/// let result = extend::<IdentityBrand, _, _>(|id| id.0 * 2, w);
305	/// assert_eq!(result, Identity(20));
306	///
307	/// // extend on Vec: apply f to each suffix
308	/// let v = vec![1, 2, 3];
309	/// let sums = extend::<VecBrand, _, _>(|s: Vec<i32>| s.iter().sum::<i32>(), v);
310	/// assert_eq!(sums, vec![6, 5, 3]);
311	/// ```
312	pub fn extend<'a, Brand: Extend, A: 'a + Clone, B: 'a>(
313		f: impl Fn(Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
314		wa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
315	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
316		Brand::extend(f, wa)
317	}
318
319	/// Duplicates a comonadic context, wrapping it inside another layer of the same context.
320	///
321	/// `duplicate(wa)` is equivalent to `extend(identity, wa)`. It is the dual of
322	/// [`join`](crate::functions::join) for monads.
323	///
324	/// Produces `F<F<A>>` from `F<A>`, embedding the original context as the inner value.
325	#[document_signature]
326	///
327	#[document_type_parameters(
328		"The lifetime of the values.",
329		"The brand of the comonadic context.",
330		"The type of the value(s) inside the comonadic context."
331	)]
332	///
333	#[document_parameters("The comonadic context to duplicate.")]
334	///
335	#[document_returns("A doubly-wrapped comonadic context.")]
336	#[document_examples]
337	///
338	/// ```
339	/// use fp_library::{
340	/// 	brands::*,
341	/// 	functions::*,
342	/// };
343	///
344	/// // duplicate on Vec produces all suffixes
345	/// let v = vec![1, 2, 3];
346	/// let d = duplicate::<VecBrand, _>(v);
347	/// assert_eq!(d, vec![vec![1, 2, 3], vec![2, 3], vec![3]]);
348	/// ```
349	pub fn duplicate<'a, Brand: Extend, A: 'a + Clone>(
350		wa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
351	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)>)
352	where
353		Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>): 'a, {
354		Brand::duplicate(wa)
355	}
356
357	/// Forwards co-Kleisli composition.
358	///
359	/// Composes two co-Kleisli functions left-to-right: first applies `f` via
360	/// [`extend()`](crate::classes::extend::extend), then applies `g` to the result. This is the dual of
361	/// [`compose_kleisli`](crate::functions::compose_kleisli).
362	#[document_signature]
363	///
364	#[document_type_parameters(
365		"The lifetime of the values.",
366		"The brand of the comonadic context.",
367		"The type of the value(s) inside the comonadic context.",
368		"The result type of the first co-Kleisli function.",
369		"The result type of the second co-Kleisli function."
370	)]
371	///
372	#[document_parameters(
373		"The first co-Kleisli function.",
374		"The second co-Kleisli function.",
375		"The comonadic context to operate on."
376	)]
377	///
378	#[document_returns(
379		"The result of composing both co-Kleisli functions and applying them to the context."
380	)]
381	#[document_examples]
382	///
383	/// ```
384	/// use fp_library::{
385	/// 	brands::*,
386	/// 	functions::*,
387	/// 	types::*,
388	/// };
389	///
390	/// let f = |id: Identity<i32>| id.0 + 1;
391	/// let g = |id: Identity<i32>| id.0 * 10;
392	/// let w = Identity(5);
393	/// // compose_co_kleisli(f, g, w): extend f, then apply g
394	/// let result = compose_co_kleisli::<IdentityBrand, _, _, _>(f, g, w);
395	/// assert_eq!(result, 60);
396	/// ```
397	pub fn compose_co_kleisli<'a, Brand: Extend, A: 'a + Clone, B: 'a + Clone, C: 'a>(
398		f: impl Fn(Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
399		g: impl Fn(Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)) -> C + 'a,
400		wa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
401	) -> C {
402		Brand::compose_co_kleisli(f, g, wa)
403	}
404
405	/// Backwards co-Kleisli composition.
406	///
407	/// Composes two co-Kleisli functions right-to-left: first applies `g` via
408	/// [`extend()`](crate::classes::extend::extend), then applies `f` to the result. This is the dual of
409	/// [`compose_kleisli_flipped`](crate::functions::compose_kleisli_flipped).
410	#[document_signature]
411	///
412	#[document_type_parameters(
413		"The lifetime of the values.",
414		"The brand of the comonadic context.",
415		"The type of the value(s) inside the comonadic context.",
416		"The result type of the second co-Kleisli function (applied first).",
417		"The result type of the first co-Kleisli function (applied second)."
418	)]
419	///
420	#[document_parameters(
421		"The second co-Kleisli function (applied after `g`).",
422		"The first co-Kleisli function (applied first to the context).",
423		"The comonadic context to operate on."
424	)]
425	///
426	#[document_returns(
427		"The result of composing both co-Kleisli functions and applying them to the context."
428	)]
429	#[document_examples]
430	///
431	/// ```
432	/// use fp_library::{
433	/// 	brands::*,
434	/// 	functions::*,
435	/// 	types::*,
436	/// };
437	///
438	/// let f = |id: Identity<i32>| id.0 * 10;
439	/// let g = |id: Identity<i32>| id.0 + 1;
440	/// let w = Identity(5);
441	/// // compose_co_kleisli_flipped(f, g, w): extend g, then apply f
442	/// let result = compose_co_kleisli_flipped::<IdentityBrand, _, _, _>(f, g, w);
443	/// assert_eq!(result, 60);
444	/// ```
445	pub fn compose_co_kleisli_flipped<'a, Brand: Extend, A: 'a + Clone, B: 'a + Clone, C: 'a>(
446		f: impl Fn(Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)) -> C + 'a,
447		g: impl Fn(Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
448		wa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
449	) -> C {
450		Brand::compose_co_kleisli_flipped(f, g, wa)
451	}
452
453	/// Extends with the arguments flipped.
454	///
455	/// A version of [`extend()`](crate::classes::extend::extend) where the comonadic context comes first, followed
456	/// by the extension function. Useful for pipelines where the value is known
457	/// before the function.
458	#[document_signature]
459	///
460	#[document_type_parameters(
461		"The lifetime of the values.",
462		"The brand of the comonadic context.",
463		"The type of the value(s) inside the comonadic context.",
464		"The result type of the extension function."
465	)]
466	///
467	#[document_parameters(
468		"The comonadic context to extend over.",
469		"The function that consumes a whole context and produces a value."
470	)]
471	///
472	#[document_returns("A new comonadic context containing the results of applying the function.")]
473	#[document_examples]
474	///
475	/// ```
476	/// use fp_library::{
477	/// 	brands::*,
478	/// 	functions::*,
479	/// 	types::*,
480	/// };
481	///
482	/// let w = Identity(5);
483	/// let result = extend_flipped::<IdentityBrand, _, _>(w, |id| id.0 * 3);
484	/// assert_eq!(result, Identity(15));
485	/// ```
486	pub fn extend_flipped<'a, Brand: Extend, A: 'a + Clone, B: 'a>(
487		wa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
488		f: impl Fn(Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
489	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
490		Brand::extend_flipped(wa, f)
491	}
492}
493
494pub use inner::*;