Skip to main content

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