solverforge_scoring/stream/collection_extract.rs
1/* CollectionExtract trait for ergonomic entity collection extraction.
2
3Allows extractor closures to return either `&[A]` or `&Vec<A>`,
4so users can write `vec(|s| &s.employees)` without `.as_slice()`.
5
6# Usage
7
8```
9use solverforge_scoring::stream::collection_extract::{CollectionExtract, VecExtract, vec};
10
11struct Schedule { employees: Vec<String> }
12
13// Direct slice closure — works out of the box:
14let e1 = |s: &Schedule| s.employees.as_slice();
15let _: &[String] = e1.extract(&Schedule { employees: vec![] });
16
17// Vec reference closure — wrap with `vec(...)`:
18let e2 = vec(|s: &Schedule| &s.employees);
19let _: &[String] = e2.extract(&Schedule { employees: vec![] });
20```
21*/
22
23/* Extracts a slice of entities from the solution.
24
25The associated type `Item` names the entity type, allowing callers to
26write `E: CollectionExtract<S, Item = A>` when `A` must be inferred from `E`
27rather than stated as a separate generic parameter.
28*/
29pub trait CollectionExtract<S>: Send + Sync {
30 // The entity type yielded by this extractor.
31 type Item;
32
33 // Extracts the entity slice from the solution.
34 fn extract<'s>(&self, s: &'s S) -> &'s [Self::Item];
35}
36
37impl<S, A, F> CollectionExtract<S> for F
38where
39 F: Fn(&S) -> &[A] + Send + Sync,
40{
41 type Item = A;
42
43 #[inline]
44 fn extract<'s>(&self, s: &'s S) -> &'s [A] {
45 self(s)
46 }
47}
48
49/* Wraps a `Fn(&S) -> &Vec<A>` closure so it satisfies `CollectionExtract<S>`.
50
51Construct via the [`vec`] free function.
52*/
53pub struct VecExtract<F>(pub F);
54
55impl<S, A, F> CollectionExtract<S> for VecExtract<F>
56where
57 F: Fn(&S) -> &Vec<A> + Send + Sync,
58{
59 type Item = A;
60
61 #[inline]
62 fn extract<'s>(&self, s: &'s S) -> &'s [A] {
63 (self.0)(s).as_slice()
64 }
65}
66
67/* Wraps a `Fn(&S) -> &Vec<A>` closure into a [`VecExtract`] that satisfies
68[`CollectionExtract<S>`].
69
70Use this when your solution field is a `Vec<A>` and you want to write
71`|s| &s.field` instead of `|s| s.field.as_slice()`.
72
73# Example
74
75```
76use solverforge_scoring::stream::collection_extract::{CollectionExtract, vec};
77
78struct Schedule { employees: Vec<String> }
79
80let extractor = vec(|s: &Schedule| &s.employees);
81let schedule = Schedule { employees: vec!["Alice".into()] };
82assert_eq!(extractor.extract(&schedule), &["Alice".to_string()]);
83```
84*/
85pub fn vec<S, A, F>(f: F) -> VecExtract<F>
86where
87 F: Fn(&S) -> &Vec<A> + Send + Sync,
88{
89 VecExtract(f)
90}