fp_library/classes/profunctor.rs
1//! Profunctors, which are functors contravariant in the first argument and covariant in the second.
2//!
3//! A profunctor represents a morphism between two categories, mapping objects and morphisms from one to the other.
4//!
5//! ### Examples
6//!
7//! ```
8//! use fp_library::{
9//! brands::*,
10//! functions::*,
11//! };
12//!
13//! // Arrow is a profunctor
14//! let f = |x: i32| x + 1;
15//! let g = dimap::<RcFnBrand, _, _, _, _>(
16//! |x: i32| x * 2,
17//! |x: i32| x - 1,
18//! std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
19//! );
20//! assert_eq!(g(10), 20); // (10 * 2) + 1 - 1 = 20
21//! ```
22
23pub use {
24 choice::*,
25 closed::*,
26 cochoice::*,
27 costrong::*,
28 strong::*,
29 wander::*,
30};
31
32pub mod choice;
33pub mod closed;
34pub mod cochoice;
35pub mod costrong;
36pub mod strong;
37pub mod wander;
38
39#[fp_macros::document_module]
40mod inner {
41 use {
42 crate::{
43 brands::*,
44 classes::*,
45 kinds::*,
46 },
47 fp_macros::*,
48 };
49
50 /// A type class for profunctors.
51 ///
52 /// A profunctor is a type constructor that is contravariant in its first type parameter
53 /// and covariant in its second type parameter. This means it can pre-compose with a
54 /// function on the input and post-compose with a function on the output.
55 ///
56 /// ### Hierarchy Unification
57 ///
58 /// This trait is the root of the unified profunctor and arrow hierarchies on
59 /// [`Kind!(type Of<'a, A: 'a, B: 'a>: 'a;)`](crate::kinds::Kind_266801a817966495).
60 /// This unification ensures that all profunctor-based abstractions
61 /// (including lenses and prisms) share a consistent higher-kinded representation with
62 /// strict lifetime bounds.
63 ///
64 /// By explicitly requiring that both type parameters outlive the application lifetime `'a`,
65 /// we provide the compiler with the necessary guarantees to handle trait objects
66 /// (like `dyn Fn`) commonly used in profunctor implementations. This resolves potential
67 /// E0310 errors where the compiler cannot otherwise prove that captured variables in
68 /// closures satisfy the required lifetime bounds.
69 ///
70 /// ### Laws
71 ///
72 /// `Profunctor` instances must satisfy the following laws:
73 /// * Identity: `dimap(identity, identity, p) = p`.
74 /// * Composition: `dimap(f2 ∘ f1, g1 ∘ g2, p) = dimap(f1, g1, dimap(f2, g2, p))`.
75 #[document_examples]
76 ///
77 /// Profunctor laws for [`RcFnBrand`](crate::brands::RcFnBrand):
78 ///
79 /// ```
80 /// use fp_library::{
81 /// brands::*,
82 /// functions::*,
83 /// };
84 ///
85 /// let p = std::rc::Rc::new(|x: i32| x + 1) as std::rc::Rc<dyn Fn(i32) -> i32>;
86 ///
87 /// // Identity: dimap(identity, identity, p) = p
88 /// let id_mapped = dimap::<RcFnBrand, _, _, _, _>(identity, identity, p.clone());
89 /// assert_eq!(id_mapped(5), p(5));
90 /// assert_eq!(id_mapped(0), p(0));
91 ///
92 /// // Composition: dimap(f2 ∘ f1, g1 ∘ g2, p)
93 /// // = dimap(f1, g1, dimap(f2, g2, p))
94 /// let f1 = |x: i32| x + 10;
95 /// let f2 = |x: i32| x * 2;
96 /// let g1 = |x: i32| x - 1;
97 /// let g2 = |x: i32| x * 3;
98 /// let left = dimap::<RcFnBrand, _, _, _, _>(
99 /// compose(f2, f1), // f2 ∘ f1
100 /// compose(g1, g2), // g1 ∘ g2
101 /// p.clone(),
102 /// );
103 /// let right = dimap::<RcFnBrand, _, _, _, _>(f1, g1, dimap::<RcFnBrand, _, _, _, _>(f2, g2, p));
104 /// assert_eq!(left(5), right(5));
105 /// assert_eq!(left(0), right(0));
106 /// ```
107 #[kind(type Of<'a, A: 'a, B: 'a>: 'a;)]
108 pub trait Profunctor {
109 /// Maps over both arguments of the profunctor.
110 ///
111 /// This method applies a contravariant function to the first argument and a covariant
112 /// function to the second argument, transforming the profunctor.
113 #[document_signature]
114 ///
115 #[document_type_parameters(
116 "The lifetime of the values.",
117 "The new input type (contravariant position).",
118 "The original input type.",
119 "The original output type.",
120 "The new output type (covariant position)."
121 )]
122 ///
123 #[document_parameters(
124 "The contravariant function to apply to the input.",
125 "The covariant function to apply to the output.",
126 "The profunctor instance."
127 )]
128 ///
129 #[document_returns("A new profunctor instance with transformed input and output types.")]
130 #[document_examples]
131 ///
132 /// ```
133 /// use fp_library::{
134 /// brands::*,
135 /// classes::profunctor::*,
136 /// functions::*,
137 /// };
138 ///
139 /// let f = |x: i32| x + 1;
140 /// let g = dimap::<RcFnBrand, _, _, _, _>(
141 /// |x: i32| x * 2,
142 /// |x: i32| x - 1,
143 /// std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
144 /// );
145 /// assert_eq!(g(10), 20); // (10 * 2) + 1 - 1 = 20
146 /// ```
147 fn dimap<'a, A: 'a, B: 'a, C: 'a, D: 'a>(
148 ab: impl Fn(A) -> B + 'a,
149 cd: impl Fn(C) -> D + 'a,
150 pbc: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, B, C>),
151 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, D>);
152
153 /// Maps contravariantly over the first argument.
154 ///
155 /// This is a convenience method that maps only over the input (contravariant position).
156 /// Corresponds to `lmap` in Haskell and `lcmap` in PureScript.
157 #[document_signature]
158 ///
159 #[document_type_parameters(
160 "The lifetime of the values.",
161 "The new input type.",
162 "The original input type.",
163 "The output type."
164 )]
165 ///
166 #[document_parameters(
167 "The contravariant function to apply to the input.",
168 "The profunctor instance."
169 )]
170 ///
171 #[document_returns("A new profunctor instance with transformed input type.")]
172 #[document_examples]
173 ///
174 /// ```
175 /// use fp_library::{
176 /// brands::*,
177 /// classes::profunctor::*,
178 /// functions::*,
179 /// };
180 ///
181 /// let f = |x: i32| x + 1;
182 /// let g = map_input::<RcFnBrand, _, _, _>(
183 /// |x: i32| x * 2,
184 /// std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
185 /// );
186 /// assert_eq!(g(10), 21); // (10 * 2) + 1 = 21
187 /// ```
188 fn map_input<'a, A: 'a, B: 'a, C: 'a>(
189 ab: impl Fn(A) -> B + 'a,
190 pbc: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, B, C>),
191 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, C>) {
192 Self::dimap(ab, crate::functions::identity, pbc)
193 }
194
195 /// Maps covariantly over the second argument.
196 ///
197 /// This is a convenience method that maps only over the output (covariant position).
198 /// Corresponds to `rmap` in both Haskell and PureScript.
199 #[document_signature]
200 ///
201 #[document_type_parameters(
202 "The lifetime of the values.",
203 "The input type.",
204 "The original output type.",
205 "The new output type."
206 )]
207 ///
208 #[document_parameters(
209 "The covariant function to apply to the output.",
210 "The profunctor instance."
211 )]
212 ///
213 #[document_returns("A new profunctor instance with transformed output type.")]
214 #[document_examples]
215 ///
216 /// ```
217 /// use fp_library::{
218 /// brands::*,
219 /// classes::profunctor::*,
220 /// functions::*,
221 /// };
222 ///
223 /// let f = |x: i32| x + 1;
224 /// let g = map_output::<RcFnBrand, _, _, _>(
225 /// |x: i32| x * 2,
226 /// std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
227 /// );
228 /// assert_eq!(g(10), 22); // (10 + 1) * 2 = 22
229 /// ```
230 fn map_output<'a, A: 'a, B: 'a, C: 'a>(
231 bc: impl Fn(B) -> C + 'a,
232 pab: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>),
233 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, C>) {
234 Self::dimap(crate::functions::identity, bc, pab)
235 }
236 }
237
238 /// Maps over both arguments of the profunctor.
239 ///
240 /// Free function version that dispatches to [the type class' associated function][`Profunctor::dimap`].
241 #[document_signature]
242 ///
243 #[document_type_parameters(
244 "The lifetime of the values.",
245 "The brand of the profunctor.",
246 "The new input type (contravariant position).",
247 "The original input type.",
248 "The original output type.",
249 "The new output type (covariant position)."
250 )]
251 ///
252 #[document_parameters(
253 "The contravariant function to apply to the input.",
254 "The covariant function to apply to the output.",
255 "The profunctor instance."
256 )]
257 ///
258 #[document_returns("A new profunctor instance with transformed input and output types.")]
259 #[document_examples]
260 ///
261 /// ```
262 /// use fp_library::{
263 /// brands::*,
264 /// classes::profunctor::*,
265 /// functions::*,
266 /// };
267 ///
268 /// let f = |x: i32| x + 1;
269 /// let g = dimap::<RcFnBrand, _, _, _, _>(
270 /// |x: i32| x * 2,
271 /// |x: i32| x - 1,
272 /// std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
273 /// );
274 /// assert_eq!(g(10), 20); // (10 * 2) + 1 - 1 = 20
275 /// ```
276 pub fn dimap<'a, Brand: Profunctor, A: 'a, B: 'a, C: 'a, D: 'a>(
277 ab: impl Fn(A) -> B + 'a,
278 cd: impl Fn(C) -> D + 'a,
279 pbc: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, B, C>),
280 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, D>) {
281 Brand::dimap(ab, cd, pbc)
282 }
283
284 /// Maps contravariantly over the first argument.
285 ///
286 /// Corresponds to `lmap` in Haskell and `lcmap` in PureScript.
287 ///
288 /// Free function version that dispatches to [the type class' associated function][`Profunctor::map_input`].
289 #[document_signature]
290 ///
291 #[document_type_parameters(
292 "The lifetime of the values.",
293 "The brand of the profunctor.",
294 "The new input type.",
295 "The original input type.",
296 "The output type."
297 )]
298 ///
299 #[document_parameters(
300 "The contravariant function to apply to the input.",
301 "The profunctor instance."
302 )]
303 ///
304 #[document_returns("A new profunctor instance with transformed input type.")]
305 #[document_examples]
306 ///
307 /// ```
308 /// use fp_library::{
309 /// brands::*,
310 /// classes::profunctor::*,
311 /// functions::*,
312 /// };
313 ///
314 /// let f = |x: i32| x + 1;
315 /// let g = map_input::<RcFnBrand, _, _, _>(
316 /// |x: i32| x * 2,
317 /// std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
318 /// );
319 /// assert_eq!(g(10), 21); // (10 * 2) + 1 = 21
320 /// ```
321 pub fn map_input<'a, Brand: Profunctor, A: 'a, B: 'a, C: 'a>(
322 ab: impl Fn(A) -> B + 'a,
323 pbc: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, B, C>),
324 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, C>) {
325 Brand::map_input(ab, pbc)
326 }
327
328 /// Maps covariantly over the second argument.
329 ///
330 /// Corresponds to `rmap` in both Haskell and PureScript.
331 ///
332 /// Free function version that dispatches to [the type class' associated function][`Profunctor::map_output`].
333 #[document_signature]
334 ///
335 #[document_type_parameters(
336 "The lifetime of the values.",
337 "The brand of the profunctor.",
338 "The input type.",
339 "The original output type.",
340 "The new output type."
341 )]
342 ///
343 #[document_parameters(
344 "The covariant function to apply to the output.",
345 "The profunctor instance."
346 )]
347 ///
348 #[document_returns("A new profunctor instance with transformed output type.")]
349 #[document_examples]
350 ///
351 /// ```
352 /// use fp_library::{
353 /// brands::*,
354 /// classes::profunctor::*,
355 /// functions::*,
356 /// };
357 ///
358 /// let f = |x: i32| x + 1;
359 /// let g = map_output::<RcFnBrand, _, _, _>(
360 /// |x: i32| x * 2,
361 /// std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>,
362 /// );
363 /// assert_eq!(g(10), 22); // (10 + 1) * 2 = 22
364 /// ```
365 pub fn map_output<'a, Brand: Profunctor, A: 'a, B: 'a, C: 'a>(
366 bc: impl Fn(B) -> C + 'a,
367 pab: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>),
368 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, C>) {
369 Brand::map_output(bc, pab)
370 }
371
372 /// Lifts a pure function into a profunctor context.
373 ///
374 /// Given a type that is both a [`Category`] (providing `identity`) and a
375 /// [`Profunctor`] (providing `map_output`), this function lifts a pure function
376 /// `A -> B` into the profunctor as `map_output(f, identity())`.
377 #[document_signature]
378 ///
379 #[document_type_parameters(
380 "The lifetime of the function and its captured data.",
381 "The brand of the profunctor.",
382 "The input type.",
383 "The output type."
384 )]
385 ///
386 #[document_parameters("The closure to lift.")]
387 ///
388 #[document_returns("The lifted profunctor value.")]
389 #[document_examples]
390 ///
391 /// ```
392 /// use fp_library::{
393 /// brands::*,
394 /// functions::*,
395 /// };
396 ///
397 /// let f = arrow::<RcFnBrand, _, _>(|x: i32| x * 2);
398 /// assert_eq!(f(5), 10);
399 /// ```
400 pub fn arrow<'a, Brand, A, B: 'a>(
401 f: impl 'a + Fn(A) -> B
402 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>)
403 where
404 Brand: Category + Profunctor, {
405 Brand::map_output(f, Brand::identity())
406 }
407
408 crate::impl_kind! {
409 impl<Brand: Profunctor, A: 'static> for ProfunctorFirstAppliedBrand<Brand, A> {
410 type Of<'a, B: 'a>: 'a = Apply!(<Brand as Kind!(type Of<'a, T: 'a, U: 'a>: 'a;)>::Of<'a, A, B>);
411 }
412 }
413
414 /// [`Functor`] instance for [`ProfunctorFirstAppliedBrand`].
415 ///
416 /// Maps over the second (covariant) type parameter of a profunctor via [`Profunctor::map_output`].
417 #[document_type_parameters("The profunctor brand.", "The fixed first type parameter.")]
418 impl<Brand: Profunctor, A: 'static> Functor for ProfunctorFirstAppliedBrand<Brand, A> {
419 /// Map a function over the covariant type parameter.
420 #[document_signature]
421 #[document_type_parameters(
422 "The lifetime of the values.",
423 "The input type.",
424 "The output type."
425 )]
426 #[document_parameters("The function to apply.", "The profunctor value to map over.")]
427 #[document_returns("The mapped profunctor value.")]
428 #[document_examples]
429 ///
430 /// ```
431 /// use fp_library::{
432 /// brands::*,
433 /// functions::explicit::*,
434 /// };
435 ///
436 /// let f = std::rc::Rc::new(|x: i32| x + 1) as std::rc::Rc<dyn Fn(i32) -> i32>;
437 /// let g = map::<ProfunctorFirstAppliedBrand<RcFnBrand, i32>, _, _, _, _>(|x: i32| x * 2, f);
438 /// assert_eq!(g(5), 12); // (5 + 1) * 2
439 /// ```
440 fn map<'a, B: 'a, C: 'a>(
441 f: impl Fn(B) -> C + 'a,
442 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
443 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) {
444 Brand::map_output(f, fa)
445 }
446 }
447
448 impl_kind! {
449 impl<Brand: Profunctor, B: 'static> for ProfunctorSecondAppliedBrand<Brand, B> {
450 type Of<'a, A: 'a>: 'a = Apply!(<Brand as Kind!(type Of<'a, T: 'a, U: 'a>: 'a;)>::Of<'a, A, B>);
451 }
452 }
453
454 /// [`Contravariant`] instance for [`ProfunctorSecondAppliedBrand`].
455 ///
456 /// Contramaps over the first (contravariant) type parameter of a profunctor via [`Profunctor::map_input`].
457 #[document_type_parameters("The profunctor brand.", "The fixed second type parameter.")]
458 impl<Brand: Profunctor, B: 'static> Contravariant for ProfunctorSecondAppliedBrand<Brand, B> {
459 /// Contramap a function over the contravariant type parameter.
460 #[document_signature]
461 #[document_type_parameters(
462 "The lifetime of the values.",
463 "The input type.",
464 "The output type."
465 )]
466 #[document_parameters("The function to apply.", "The profunctor value to contramap over.")]
467 #[document_returns("The contramapped profunctor value.")]
468 #[document_examples]
469 ///
470 /// ```
471 /// use fp_library::{
472 /// brands::*,
473 /// functions::explicit::contramap,
474 /// };
475 ///
476 /// let f = std::rc::Rc::new(|x: i32| x + 1) as std::rc::Rc<dyn Fn(i32) -> i32>;
477 /// let g =
478 /// contramap::<ProfunctorSecondAppliedBrand<RcFnBrand, i32>, _, _, _, _>(|x: i32| x * 2, f);
479 /// assert_eq!(g(5), 11); // (5 * 2) + 1
480 /// ```
481 fn contramap<'a, A: 'a, C: 'a>(
482 f: impl Fn(C) -> A + 'a,
483 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
484 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>) {
485 Brand::map_input(f, fa)
486 }
487 }
488}
489
490pub use inner::*;