fp_library/classes/profunctor/strong.rs
1//! Strong profunctors, which can lift profunctors through product types.
2//!
3//! A strong profunctor allows lifting a profunctor `P A B` to `P (A, C) (B, C)`,
4//! preserving the extra context `C`. This is the key constraint for lenses.
5//!
6//! ### Examples
7//!
8//! ```
9//! use fp_library::{
10//! brands::*,
11//! classes::profunctor::*,
12//! functions::*,
13//! };
14//!
15//! // Functions are strong profunctors
16//! let f = |x: i32| x + 1;
17//! let g = first::<RcFnBrand, _, _, i32>(std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>);
18//! assert_eq!(g((10, 20)), (11, 20));
19//! ```
20
21#[fp_macros::document_module]
22mod inner {
23 use {
24 crate::{
25 classes::*,
26 kinds::*,
27 },
28 fp_macros::*,
29 };
30
31 /// A type class for strong profunctors.
32 ///
33 /// A strong profunctor can lift a profunctor through product types (tuples).
34 /// This is the profunctor constraint that characterizes lenses.
35 ///
36 /// ### Hierarchy Unification
37 ///
38 /// This trait uses the strict Kind signature from [`Kind!(type Of<'a, A: 'a, B: 'a>: 'a;)`](crate::kinds::Kind_266801a817966495). This ensures
39 /// that when lifting a profunctor, the secondary component of the product type (the context)
40 /// correctly satisfies lifetime requirements relative to the profunctor's application.
41 ///
42 /// ### Laws
43 ///
44 /// `Strong` instances must satisfy the following laws:
45 /// * Identity: `first(identity) = identity`.
46 /// * Composition: `first(p ∘ q) = first(p) ∘ first(q)`.
47 /// * Naturality: `dimap(fst, fst) ∘ first(p) = first(p) ∘ dimap(fst, fst)`.
48 #[document_examples]
49 ///
50 /// Strong laws for [`RcFnBrand`](crate::brands::RcFnBrand):
51 ///
52 /// ```
53 /// use fp_library::{
54 /// brands::*,
55 /// classes::profunctor::*,
56 /// functions::*,
57 /// };
58 ///
59 /// let p = lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2 + 1);
60 /// let q = lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 10);
61 ///
62 /// // Identity: first(identity) = identity
63 /// let id = category_identity::<RcFnBrand, i32>();
64 /// let first_id = first::<RcFnBrand, _, _, String>(id);
65 /// assert_eq!(first_id((5, "hi".to_string())), (5, "hi".to_string()));
66 ///
67 /// // Composition: first(p ∘ q) = first(p) ∘ first(q)
68 /// let lhs = first::<RcFnBrand, _, _, String>(semigroupoid_compose::<RcFnBrand, _, _, _>(
69 /// p.clone(),
70 /// q.clone(),
71 /// ));
72 /// let rhs = semigroupoid_compose::<RcFnBrand, _, _, _>(
73 /// first::<RcFnBrand, _, _, String>(p),
74 /// first::<RcFnBrand, _, _, String>(q),
75 /// );
76 /// assert_eq!(lhs((5, "hi".to_string())), rhs((5, "hi".to_string())));
77 /// assert_eq!(lhs((0, "lo".to_string())), rhs((0, "lo".to_string())));
78 /// ```
79 pub trait Strong: Profunctor {
80 /// Lift a profunctor to operate on the first component of a pair.
81 ///
82 /// This method takes a profunctor `P A B` and returns `P (A, C) (B, C)`,
83 /// threading the extra context `C` through unchanged.
84 #[document_signature]
85 ///
86 #[document_type_parameters(
87 "The lifetime of the values.",
88 "The input type of the profunctor.",
89 "The output type of the profunctor.",
90 "The type of the second component (threaded through unchanged)."
91 )]
92 ///
93 #[document_parameters("The profunctor instance to lift.")]
94 ///
95 #[document_returns("A new profunctor that operates on pairs.")]
96 #[document_examples]
97 ///
98 /// ```
99 /// use fp_library::{
100 /// brands::*,
101 /// classes::profunctor::*,
102 /// functions::*,
103 /// };
104 ///
105 /// let f = |x: i32| x + 1;
106 /// let g = first::<RcFnBrand, _, _, i32>(std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>);
107 /// assert_eq!(g((10, 20)), (11, 20));
108 /// ```
109 fn first<'a, A: 'a, B: 'a, C: 'a>(
110 pab: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>)
111 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, (A, C), (B, C)>);
112
113 /// Lift a profunctor to operate on the second component of a pair.
114 ///
115 /// This method takes a profunctor `P A B` and returns `P (C, A) (C, B)`,
116 /// threading the extra context `C` through unchanged in the first position.
117 #[document_signature]
118 ///
119 #[document_type_parameters(
120 "The lifetime of the values.",
121 "The input type of the profunctor.",
122 "The output type of the profunctor.",
123 "The type of the first component (threaded through unchanged)."
124 )]
125 ///
126 #[document_parameters("The profunctor instance to lift.")]
127 ///
128 #[document_returns("A new profunctor that operates on pairs.")]
129 #[document_examples]
130 ///
131 /// ```
132 /// use fp_library::{
133 /// brands::*,
134 /// classes::profunctor::*,
135 /// functions::*,
136 /// };
137 ///
138 /// let f = |x: i32| x + 1;
139 /// let g = second::<RcFnBrand, _, _, i32>(std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>);
140 /// assert_eq!(g((20, 10)), (20, 11));
141 /// ```
142 fn second<'a, A: 'a, B: 'a, C: 'a>(
143 pab: Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>)
144 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, (C, A), (C, B)>) {
145 Self::dimap(|(c, a)| (a, c), |(b, c)| (c, b), Self::first(pab))
146 }
147 }
148
149 /// Lift a profunctor to operate on the first component of a pair.
150 ///
151 /// Free function version that dispatches to [the type class' associated function][`Strong::first`].
152 #[document_signature]
153 ///
154 #[document_type_parameters(
155 "The lifetime of the values.",
156 "The brand of the strong profunctor.",
157 "The input type of the profunctor.",
158 "The output type of the profunctor.",
159 "The type of the second component (threaded through unchanged)."
160 )]
161 ///
162 #[document_parameters("The profunctor instance to lift.")]
163 ///
164 #[document_returns("A new profunctor that operates on pairs.")]
165 #[document_examples]
166 ///
167 /// ```
168 /// use fp_library::{
169 /// brands::*,
170 /// classes::profunctor::*,
171 /// functions::*,
172 /// };
173 ///
174 /// let f = |x: i32| x + 1;
175 /// let g = first::<RcFnBrand, _, _, i32>(std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>);
176 /// assert_eq!(g((10, 20)), (11, 20));
177 /// ```
178 pub fn first<'a, Brand: Strong, A: 'a, B: 'a, C: 'a>(
179 pab: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>)
180 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, (A, C), (B, C)>) {
181 Brand::first(pab)
182 }
183
184 /// Lift a profunctor to operate on the second component of a pair.
185 ///
186 /// Free function version that dispatches to [the type class' associated function][`Strong::second`].
187 #[document_signature]
188 ///
189 #[document_type_parameters(
190 "The lifetime of the values.",
191 "The brand of the strong profunctor.",
192 "The input type of the profunctor.",
193 "The output type of the profunctor.",
194 "The type of the first component (threaded through unchanged)."
195 )]
196 ///
197 #[document_parameters("The profunctor instance to lift.")]
198 ///
199 #[document_returns("A new profunctor that operates on pairs.")]
200 #[document_examples]
201 ///
202 /// ```
203 /// use fp_library::{
204 /// brands::*,
205 /// classes::profunctor::*,
206 /// functions::*,
207 /// };
208 ///
209 /// let f = |x: i32| x + 1;
210 /// let g = second::<RcFnBrand, _, _, i32>(std::rc::Rc::new(f) as std::rc::Rc<dyn Fn(i32) -> i32>);
211 /// assert_eq!(g((20, 10)), (20, 11));
212 /// ```
213 pub fn second<'a, Brand: Strong, A: 'a, B: 'a, C: 'a>(
214 pab: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>)
215 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, (C, A), (C, B)>) {
216 Brand::second(pab)
217 }
218
219 /// Compose a value acting on a pair from two values, each acting on one
220 /// component of the pair.
221 ///
222 /// Equivalent to PureScript's `splitStrong` / `(***)`.
223 #[document_signature]
224 ///
225 #[document_type_parameters(
226 "The lifetime of the values.",
227 "The brand of the strong profunctor.",
228 "The input type of the first profunctor.",
229 "The output type of the first profunctor.",
230 "The input type of the second profunctor.",
231 "The output type of the second profunctor."
232 )]
233 ///
234 #[document_parameters(
235 "The profunctor acting on the first component.",
236 "The profunctor acting on the second component."
237 )]
238 ///
239 #[document_returns(
240 "A new profunctor that maps the first component via `l` and the second via `r`."
241 )]
242 #[document_examples]
243 ///
244 /// ```
245 /// use fp_library::{
246 /// brands::*,
247 /// classes::profunctor::*,
248 /// functions::*,
249 /// };
250 ///
251 /// let f = lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1);
252 /// let g = lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2);
253 /// let h = split_strong::<RcFnBrand, _, _, _, _>(f, g);
254 /// assert_eq!(h((10, 20)), (11, 40));
255 /// ```
256 pub fn split_strong<'a, Brand: Semigroupoid + Strong, A: 'a, B: 'a, C: 'a, D: 'a>(
257 l: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>),
258 r: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, C, D>),
259 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, (A, C), (B, D)>) {
260 Brand::compose(Brand::second(r), Brand::first(l))
261 }
262
263 /// Compose a value which introduces a pair from two values, each introducing
264 /// one side of the pair.
265 ///
266 /// Equivalent to PureScript's `fanout` / `(&&&)`.
267 ///
268 /// The `A: Clone` bound is required because Rust implementations need to clone
269 /// the input value to feed it into both profunctors.
270 #[document_signature]
271 ///
272 #[document_type_parameters(
273 "The lifetime of the values.",
274 "The brand of the strong profunctor.",
275 "The shared input type (must be Clone).",
276 "The output type of the first profunctor.",
277 "The output type of the second profunctor."
278 )]
279 ///
280 #[document_parameters(
281 "The profunctor producing the first component.",
282 "The profunctor producing the second component."
283 )]
284 ///
285 #[document_returns(
286 "A new profunctor that feeds the input to both `l` and `r`, collecting results in a pair."
287 )]
288 #[document_examples]
289 ///
290 /// ```
291 /// use fp_library::{
292 /// brands::*,
293 /// classes::profunctor::*,
294 /// functions::*,
295 /// };
296 ///
297 /// let f = lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1);
298 /// let g = lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2);
299 /// let h = fan_out::<RcFnBrand, _, _, _>(f, g);
300 /// assert_eq!(h(10), (11, 20));
301 /// ```
302 pub fn fan_out<'a, Brand: Semigroupoid + Strong, A: 'a + Clone, B: 'a, C: 'a>(
303 l: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, B>),
304 r: Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, C>),
305 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a, U: 'a>: 'a; )>::Of<'a, A, (B, C)>) {
306 Brand::map_input(|a: A| (a.clone(), a), split_strong::<Brand, A, B, A, C>(l, r))
307 }
308}
309
310pub use inner::*;