Skip to main content

fp_library/types/optics/
zipping.rs

1//! The `Zipping` profunctor, used for zipping two structures through a grate.
2//!
3//! `Zipping<FnBrand, S, T>` wraps a cloneable function `Fn((S, S)) -> T`, enabling
4//! point-wise combination of two structures via a grate optic.
5
6#[fp_macros::document_module]
7mod inner {
8	use {
9		crate::{
10			Apply,
11			brands::optics::*,
12			classes::{
13				profunctor::{
14					Closed,
15					Profunctor,
16				},
17				*,
18			},
19			impl_kind,
20			kinds::*,
21		},
22		fp_macros::*,
23	};
24
25	/// The `Zipping` profunctor.
26	///
27	/// Wraps a cloneable function `Fn((S, S)) -> T`. As a profunctor, `dimap f g z`
28	/// pre-composes both inputs with `f` (by pairing) and post-composes the output with `g`.
29	/// The `Closed` instance enables lifting `Zipping` over function types, which is the
30	/// key step in the `zip_with_of` operation for grate optics.
31	///
32	/// Matches PureScript's `newtype Zipping a b = Zipping (a -> a -> b)`.
33	#[document_type_parameters(
34		"The lifetime of the function.",
35		"The cloneable function brand.",
36		"The input type (contravariant).",
37		"The output type (covariant)."
38	)]
39	pub struct Zipping<'a, FunctionBrand: LiftFn, S: 'a, T: 'a> {
40		/// The binary function that zips two `S` values (as a pair) into a `T`.
41		pub run: <FunctionBrand as CloneFn>::Of<'a, (S, S), T>,
42	}
43
44	#[document_type_parameters(
45		"The lifetime of the function.",
46		"The cloneable function brand.",
47		"The input type.",
48		"The output type."
49	)]
50	impl<'a, FunctionBrand: LiftFn, S: 'a, T: 'a> Zipping<'a, FunctionBrand, S, T> {
51		/// Creates a new `Zipping` instance wrapping a binary function.
52		#[document_signature]
53		///
54		#[document_parameters(
55			"The binary function to wrap, taking a pair `(S, S)` and returning `T`."
56		)]
57		///
58		#[document_returns("A new instance of the type.")]
59		#[document_examples]
60		///
61		/// ```
62		/// use fp_library::{
63		/// 	brands::RcFnBrand,
64		/// 	types::optics::Zipping,
65		/// };
66		///
67		/// let z = Zipping::<RcFnBrand, i32, i32>::new(|(a, b)| a + b);
68		/// assert_eq!((z.run)((1, 2)), 3);
69		/// ```
70		pub fn new(f: impl Fn((S, S)) -> T + 'a) -> Self {
71			Zipping {
72				run: <FunctionBrand as LiftFn>::new(f),
73			}
74		}
75	}
76
77	#[document_type_parameters(
78		"The lifetime of the function.",
79		"The cloneable function brand.",
80		"The input type.",
81		"The output type."
82	)]
83	#[document_parameters("The zipping instance.")]
84	impl<'a, FunctionBrand: LiftFn, S: 'a, T: 'a> Clone for Zipping<'a, FunctionBrand, S, T> {
85		#[document_signature]
86		#[document_returns("A new `Zipping` instance that is a copy of the original.")]
87		#[document_examples]
88		///
89		/// ```
90		/// use fp_library::{
91		/// 	brands::RcFnBrand,
92		/// 	types::optics::Zipping,
93		/// };
94		///
95		/// let z = Zipping::<RcFnBrand, i32, i32>::new(|(a, b)| a + b);
96		/// let z2 = z.clone();
97		/// assert_eq!((z2.run)((3, 4)), 7);
98		/// ```
99		fn clone(&self) -> Self {
100			Zipping {
101				run: self.run.clone(),
102			}
103		}
104	}
105
106	impl_kind! {
107		impl<FunctionBrand: LiftFn + 'static> for ZippingBrand<FunctionBrand> {
108			#[document_default]
109			type Of<'a, S: 'a, T: 'a>: 'a = Zipping<'a, FunctionBrand, S, T>;
110		}
111	}
112
113	#[document_type_parameters("The cloneable function brand.")]
114	impl<FunctionBrand: LiftFn + 'static> Profunctor for ZippingBrand<FunctionBrand> {
115		/// Maps over both arguments of the `Zipping` profunctor.
116		///
117		/// Matches PureScript's `dimap f g (Zipping z) = Zipping \a1 a2 -> g (z (f a1) (f a2))`.
118		#[document_signature]
119		#[document_type_parameters(
120			"The lifetime of the values.",
121			"The new input type.",
122			"The original input type.",
123			"The original output type.",
124			"The new output type."
125		)]
126		///
127		#[document_parameters(
128			"The contravariant function to pre-compose on both inputs.",
129			"The covariant function to post-compose on the output.",
130			"The zipping instance to transform."
131		)]
132		#[document_returns("A transformed `Zipping` instance.")]
133		///
134		#[document_examples]
135		///
136		/// ```
137		/// use fp_library::{
138		/// 	brands::{
139		/// 		RcFnBrand,
140		/// 		optics::*,
141		/// 	},
142		/// 	classes::profunctor::Profunctor,
143		/// 	types::optics::Zipping,
144		/// };
145		///
146		/// let z = Zipping::<RcFnBrand, i32, i32>::new(|(a, b)| a + b);
147		/// // dimap (*2) (+1) z = \a1 a2 -> (a1*2 + a2*2) + 1
148		/// let z2 = <ZippingBrand<RcFnBrand> as Profunctor>::dimap(|x: i32| x * 2, |y: i32| y + 1, z);
149		/// assert_eq!((z2.run)((3, 4)), 15); // (3*2 + 4*2) + 1 = 15
150		/// ```
151		fn dimap<'a, A: 'a, B: 'a, C: 'a, D: 'a>(
152			ab: impl Fn(A) -> B + 'a,
153			cd: impl Fn(C) -> D + 'a,
154			pbc: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, B, C>),
155		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, D>) {
156			Zipping::new(move |(a1, a2)| cd((*pbc.run)((ab(a1), ab(a2)))))
157		}
158	}
159
160	#[document_type_parameters("The cloneable function brand.")]
161	impl<FunctionBrand: LiftFn + 'static> Closed<FunctionBrand> for ZippingBrand<FunctionBrand> {
162		/// Lifts `Zipping` to operate on functions.
163		///
164		/// Matches PureScript's `closed (Zipping z) = Zipping \f1 f2 x -> z (f1 x) (f2 x)`.
165		///
166		/// Given `Zipping<S, T>` (a binary function `Fn((S, S)) -> T`), produces
167		/// `Zipping<FnBrand(X, S), FnBrand(X, T)>` where the result takes two `X -> S`
168		/// functions (wrapped in `FnBrand`) and returns an `X -> T` function that applies
169		/// both to the same `x` and combines the results.
170		#[document_signature]
171		#[document_type_parameters(
172			"The lifetime of the values.",
173			"The source type of the profunctor.",
174			"The target type of the profunctor.",
175			"The shared input type for the lifted functions."
176		)]
177		///
178		#[document_parameters("The zipping instance to lift.")]
179		#[document_returns("A transformed `Zipping` instance that operates on functions.")]
180		///
181		#[document_examples]
182		///
183		/// ```
184		/// use fp_library::{
185		/// 	brands::{
186		/// 		RcFnBrand,
187		/// 		optics::*,
188		/// 	},
189		/// 	classes::{
190		/// 		profunctor::{
191		/// 			Closed,
192		/// 			Profunctor,
193		/// 		},
194		/// 		*,
195		/// 	},
196		/// 	types::optics::Zipping,
197		/// };
198		///
199		/// let z = Zipping::<RcFnBrand, i32, i32>::new(|(a, b)| a + b);
200		/// let zc = <ZippingBrand<RcFnBrand> as Closed<RcFnBrand>>::closed::<i32, i32, String>(z);
201		/// // (zc.run)(f1, f2)(x) = f1(x) + f2(x)
202		/// let f1 = <RcFnBrand as LiftFn>::new(|s: String| s.len() as i32);
203		/// let f2 = <RcFnBrand as LiftFn>::new(|s: String| s.len() as i32 * 2);
204		/// let result = (zc.run)((f1, f2));
205		/// assert_eq!(result("hi".to_string()), 6); // 2 + 4
206		/// ```
207		fn closed<'a, S: 'a, T: 'a, X: 'a + Clone>(
208			pab: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, S, T>)
209		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, <FunctionBrand as CloneFn>::Of<'a, X, S>, <FunctionBrand as CloneFn>::Of<'a, X, T>>)
210		{
211			Zipping::new(
212				move |(f1, f2): (
213					<FunctionBrand as CloneFn>::Of<'a, X, S>,
214					<FunctionBrand as CloneFn>::Of<'a, X, S>,
215				)| {
216					let run = pab.run.clone();
217					<FunctionBrand as LiftFn>::new(move |x: X| {
218						(*run)((f1(x.clone()), f2(x.clone())))
219					})
220				},
221			)
222		}
223	}
224}
225pub use inner::*;