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::*;