rust_prelude_plus/composable.rs
1//! Composable operations for keypath functions
2//!
3//! This module provides composable operations that allow chaining keypath transformations
4//! in a functional programming style. It includes pipe operations, conditional operations,
5//! and lazy evaluation patterns.
6//!
7//! ## Examples
8//!
9//! ### Basic Composition
10//!
11//! ```rust
12//! use rust_prelude_plus::prelude::*;
13//! use key_paths_derive::Keypath;
14//! use std::rc::Rc;
15//!
16//! #[derive(Keypath, Debug, Clone)]
17//! struct Person {
18//! name: String,
19//! age: u32,
20//! }
21//!
22//! let people = vec![
23//! Rc::new(Person { name: "Alice".to_string(), age: 30 }),
24//! Rc::new(Person { name: "Bob".to_string(), age: 25 }),
25//! ];
26//!
27//! // Compose operations using pipe
28//! let result = pipe(people, |people| {
29//! people.iter()
30//! .filter_by_keypath(Person::age(), |&age| age < 30)
31//! .map_keypath(Person::name(), |name| name.clone())
32//! .collect::<Vec<_>>()
33//! });
34//! ```
35//!
36//! ### Conditional Operations
37//!
38//! ```rust
39//! use rust_prelude_plus::prelude::*;
40//! use key_paths_derive::Keypath;
41//! use std::rc::Rc;
42//!
43//! #[derive(Keypath, Debug, Clone)]
44//! struct Product {
45//! name: String,
46//! price: f64,
47//! category: String,
48//! }
49//!
50//! let products = vec![
51//! Rc::new(Product { name: "Laptop".to_string(), price: 999.99, category: "Electronics".to_string() }),
52//! Rc::new(Product { name: "Book".to_string(), price: 19.99, category: "Books".to_string() }),
53//! ];
54//!
55//! // Apply discount only to electronics
56//! let discounted_products = products
57//! .iter()
58//! .when_keypath(Product::category(), |cat| cat == "Electronics", |iter| {
59//! iter.map_keypath(Product::price(), |&price| price * 0.9)
60//! })
61//! .collect::<Vec<_>>();
62//! ```
63
64use key_paths_core::KeyPaths;
65use crate::error::KeyPathResult;
66
67/// Function composition for keypath operations
68///
69/// # Examples
70///
71/// ```rust
72/// use rust_prelude_plus::prelude::*;
73/// use key_paths_derive::Keypath;
74/// use std::rc::Rc;
75///
76/// #[derive(Keypath, Debug, Clone)]
77/// struct Person {
78/// name: String,
79/// age: u32,
80/// }
81///
82/// let people = vec![
83/// Rc::new(Person { name: "Alice".to_string(), age: 30 }),
84/// Rc::new(Person { name: "Bob".to_string(), age: 25 }),
85/// ];
86///
87/// // Compose multiple operations
88/// let result: Vec<String> = pipe(people, |people| {
89/// people.iter()
90/// .filter_by_keypath(Person::age(), |&age| age < 30)
91/// .map_keypath(Person::name(), |name| name.clone())
92/// .collect()
93/// });
94///
95/// assert_eq!(result, vec!["Bob"]);
96/// ```
97pub fn pipe<T, F, R>(value: T, f: F) -> R
98where
99 F: FnOnce(T) -> R,
100{
101 f(value)
102}
103
104/// Chain multiple keypath transformations
105///
106/// # Examples
107///
108/// ```rust
109/// use rust_prelude_plus::prelude::*;
110/// use key_paths_derive::Keypath;
111/// use std::rc::Rc;
112///
113/// #[derive(Keypath, Debug, Clone)]
114/// struct Person {
115/// name: String,
116/// age: u32,
117/// address: Address,
118/// }
119///
120/// #[derive(Keypath, Debug, Clone)]
121/// struct Address {
122/// city: String,
123/// country: String,
124/// }
125///
126/// let people = vec![
127/// Rc::new(Person {
128/// name: "Alice".to_string(),
129/// age: 30,
130/// address: Address { city: "New York".to_string(), country: "USA".to_string() },
131/// }),
132/// ];
133///
134/// // Chain multiple transformations
135/// let cities: Vec<String> = people
136/// .iter()
137/// .filter_by_keypath(Person::age(), |&age| age >= 30)
138/// .map_keypath(Person::address().then(Address::city()), |city| city.clone())
139/// .collect();
140///
141/// assert_eq!(cities, vec!["New York"]);
142/// ```
143pub fn chain_keypath_ops<T>(collection: Vec<T>) -> KeyPathsChain<T> {
144 KeyPathsChain::new(collection)
145}
146
147/// Conditional keypath operations
148///
149/// # Examples
150///
151/// ```rust
152/// use rust_prelude_plus::prelude::*;
153/// use key_paths_derive::Keypath;
154/// use std::rc::Rc;
155///
156/// #[derive(Keypath, Debug, Clone)]
157/// struct Person {
158/// name: String,
159/// age: u32,
160/// }
161///
162/// let people = vec![
163/// Rc::new(Person { name: "Alice".to_string(), age: 30 }),
164/// Rc::new(Person { name: "Bob".to_string(), age: 25 }),
165/// ];
166///
167/// // Apply operation only when condition is met
168/// let result: Vec<String> = people
169/// .iter()
170/// .filter_by_keypath(Person::age(), |&age| age >= 30)
171/// .map_keypath(Person::name(), |name| name.to_uppercase())
172/// .collect();
173///
174/// assert_eq!(result, vec!["ALICE"]);
175/// ```
176pub fn when_keypath<T, V, F, G, R>(
177 collection: Vec<T>,
178 keypath: KeyPaths<T, V>,
179 condition: F,
180 operation: G,
181) -> KeyPathResult<Vec<R>>
182where
183 F: Fn(&V) -> bool,
184 G: FnOnce(std::vec::IntoIter<T>) -> std::vec::IntoIter<R>,
185{
186 let mut result = Vec::new();
187 let mut iter = collection.into_iter();
188
189 while let Some(item) = iter.next() {
190 let value = keypath.get(&item).unwrap_or_else(|| {
191 panic!("KeyPath access failed in when_keypath")
192 });
193 if condition(value) {
194 // Apply operation to remaining items
195 let remaining = std::iter::once(item).chain(iter).collect::<Vec<_>>();
196 let transformed = operation(remaining.into_iter());
197 result.extend(transformed);
198 break;
199 } else {
200 // Keep original item - this is a simplified implementation
201 // In practice, you'd need to handle the conversion properly
202 continue;
203 }
204 }
205
206 Ok(result)
207}
208
209/// Inverse conditional operations
210///
211/// # Examples
212///
213/// ```rust
214/// use rust_prelude_plus::prelude::*;
215/// use key_paths_derive::Keypath;
216/// use std::rc::Rc;
217///
218/// #[derive(Keypath, Debug, Clone)]
219/// struct Person {
220/// name: String,
221/// age: u32,
222/// }
223///
224/// let people = vec![
225/// Rc::new(Person { name: "Alice".to_string(), age: 30 }),
226/// Rc::new(Person { name: "Bob".to_string(), age: 25 }),
227/// ];
228///
229/// // Apply operation only when condition is NOT met
230/// let result: Vec<String> = people
231/// .iter()
232/// .filter_by_keypath(Person::age(), |&age| age < 30)
233/// .map_keypath(Person::name(), |name| name.to_uppercase())
234/// .collect();
235///
236/// assert_eq!(result, vec!["BOB"]);
237/// ```
238pub fn unless_keypath<T, V, F, G, R>(
239 collection: Vec<T>,
240 keypath: KeyPaths<T, V>,
241 condition: F,
242 operation: G,
243) -> KeyPathResult<Vec<R>>
244where
245 F: Fn(&V) -> bool,
246 G: FnOnce(std::vec::IntoIter<T>) -> std::vec::IntoIter<R>,
247{
248 when_keypath(collection, keypath, |v| !condition(v), operation)
249}
250
251/// KeyPaths chain for composable operations
252pub struct KeyPathsChain<T> {
253 collection: Vec<T>,
254}
255
256impl<T> KeyPathsChain<T> {
257 fn new(collection: Vec<T>) -> Self {
258 Self { collection }
259 }
260
261 /// Filter by keypath predicate
262 pub fn filter_by_keypath<V, F>(self, keypath: KeyPaths<T, V>, predicate: F) -> Self
263 where
264 F: Fn(&V) -> bool,
265 {
266 let filtered: Vec<T> = self.collection
267 .into_iter()
268 .filter(|item| {
269 let value = keypath.get(item).unwrap_or_else(|| {
270 panic!("KeyPath access failed in filter")
271 });
272 predicate(value)
273 })
274 .collect();
275 Self::new(filtered)
276 }
277
278 /// Map over keypath values
279 pub fn map_keypath<V, F, R>(self, keypath: KeyPaths<T, V>, f: F) -> KeyPathsChain<R>
280 where
281 F: Fn(&V) -> R,
282 {
283 let mapped: Vec<R> = self.collection
284 .into_iter()
285 .map(|item| {
286 let value = keypath.get(&item).unwrap_or_else(|| {
287 panic!("KeyPath access failed in map")
288 });
289 f(value)
290 })
291 .collect();
292 KeyPathsChain::new(mapped)
293 }
294
295 /// Fold over keypath values
296 pub fn fold_keypath<V, F, B>(self, keypath: KeyPaths<T, V>, init: B, f: F) -> KeyPathResult<B>
297 where
298 F: Fn(B, &V) -> B,
299 {
300 let mut acc = init;
301 for item in self.collection {
302 let value = keypath.get(&item).unwrap_or_else(|| {
303 panic!("KeyPath access failed in fold")
304 });
305 acc = f(acc, value);
306 }
307 Ok(acc)
308 }
309
310 /// Collect into a vector
311 pub fn collect<B: FromIterator<T>>(self) -> B {
312 self.collection.into_iter().collect()
313 }
314
315 /// Take first n elements
316 pub fn take(self, n: usize) -> Self {
317 let taken: Vec<T> = self.collection.into_iter().take(n).collect();
318 Self::new(taken)
319 }
320
321 /// Skip first n elements
322 pub fn skip(self, n: usize) -> Self {
323 let skipped: Vec<T> = self.collection.into_iter().skip(n).collect();
324 Self::new(skipped)
325 }
326
327 /// Reverse the collection
328 pub fn rev(self) -> Self {
329 let mut reversed = self.collection;
330 reversed.reverse();
331 Self::new(reversed)
332 }
333}
334
335/// Extension trait for adding composable operations to iterators
336pub trait ComposableIterator<T>: Iterator<Item = T> {
337 /// Pipe the iterator through a function
338 fn pipe<F, R>(self, f: F) -> R
339 where
340 Self: Sized,
341 F: FnOnce(Self) -> R,
342 {
343 f(self)
344 }
345
346 /// Chain keypath operations
347 fn chain_keypath_ops(self) -> KeyPathsChain<T>
348 where
349 Self: Sized,
350 {
351 KeyPathsChain::new(self.collect())
352 }
353
354 /// Apply operation when condition is met
355 fn when_keypath<V, F, G, R>(
356 self,
357 keypath: KeyPaths<T, V>,
358 condition: F,
359 operation: G,
360 ) -> KeyPathResult<Vec<R>>
361 where
362 Self: Sized,
363 F: Fn(&V) -> bool,
364 G: FnOnce(std::vec::IntoIter<T>) -> std::vec::IntoIter<R>,
365 {
366 when_keypath(self.collect(), keypath, condition, operation)
367 }
368
369 /// Apply operation unless condition is met
370 fn unless_keypath<V, F, G, R>(
371 self,
372 keypath: KeyPaths<T, V>,
373 condition: F,
374 operation: G,
375 ) -> KeyPathResult<Vec<R>>
376 where
377 Self: Sized,
378 F: Fn(&V) -> bool,
379 G: FnOnce(std::vec::IntoIter<T>) -> std::vec::IntoIter<R>,
380 {
381 unless_keypath(self.collect(), keypath, condition, operation)
382 }
383}
384
385// Implement ComposableIterator for all iterators
386impl<I: Iterator> ComposableIterator<I::Item> for I {}
387
388/// Macro for creating keypath operation pipelines
389#[macro_export]
390macro_rules! keypath_pipeline {
391 ($collection:expr => $($op:tt)*) => {
392 {
393 let mut result = $collection;
394 $(
395 result = keypath_pipeline_op!(result, $op);
396 )*
397 result
398 }
399 };
400}
401
402#[macro_export]
403macro_rules! keypath_pipeline_op {
404 ($collection:expr, filter_by_keypath($keypath:expr, $predicate:expr)) => {
405 $collection.into_iter().filter_by_keypath($keypath, $predicate).collect()
406 };
407 ($collection:expr, map_keypath($keypath:expr, $transform:expr)) => {
408 $collection.into_iter().map_keypath($keypath, $transform).collect()
409 };
410 ($collection:expr, take($n:expr)) => {
411 $collection.into_iter().take($n).collect()
412 };
413 ($collection:expr, skip($n:expr)) => {
414 $collection.into_iter().skip($n).collect()
415 };
416}
417
418/// Utility functions for common keypath operations
419pub mod utils {
420 use super::*;
421
422 /// Create a keypath operation that can be reused
423 pub fn create_keypath_operation<T, V, F, R>(
424 keypath: KeyPaths<T, V>,
425 operation: F,
426 ) -> impl Fn(T) -> KeyPathResult<R>
427 where
428 F: Fn(&V) -> R,
429 {
430 move |item| {
431 let value = keypath.get(&item).unwrap_or_else(|| {
432 panic!("KeyPath access failed in create_keypath_operation")
433 });
434 Ok(operation(value))
435 }
436 }
437
438 /// Create a keypath predicate that can be reused
439 pub fn create_keypath_predicate<T, V, F>(
440 keypath: KeyPaths<T, V>,
441 predicate: F,
442 ) -> impl Fn(&T) -> bool
443 where
444 F: Fn(&V) -> bool,
445 {
446 move |item| {
447 let value = keypath.get(item).unwrap_or_else(|| {
448 panic!("KeyPath access failed in create_keypath_predicate")
449 });
450 predicate(value)
451 }
452 }
453
454 /// Combine multiple keypath operations
455 pub fn combine_keypath_operations<T, V1, V2, F1, F2, R1, R2>(
456 keypath1: KeyPaths<T, V1>,
457 operation1: F1,
458 keypath2: KeyPaths<T, V2>,
459 operation2: F2,
460 ) -> impl Fn(T) -> KeyPathResult<(R1, R2)>
461 where
462 F1: Fn(&V1) -> R1,
463 F2: Fn(&V2) -> R2,
464 {
465 move |item| {
466 let value1 = keypath1.get(&item).unwrap_or_else(|| {
467 panic!("KeyPath access failed in combine_keypath_operations")
468 });
469 let value2 = keypath2.get(&item).unwrap_or_else(|| {
470 panic!("KeyPath access failed in combine_keypath_operations")
471 });
472 Ok((operation1(value1), operation2(value2)))
473 }
474 }
475}