1use karpal_std::prelude::*;
9
10#[derive(Debug, Clone)]
13struct RawRecord {
14 id: String,
15 name: String,
16 amount: String,
17 category: String,
18}
19
20#[derive(Debug, Clone)]
21struct Transaction {
22 id: u32,
23 name: String,
24 amount_cents: i64,
25 category: String,
26}
27
28#[derive(Debug, Clone)]
29struct Summary {
30 count: i64,
31 total_cents: i64,
32}
33
34impl Semigroup for Summary {
35 fn combine(self, other: Self) -> Self {
36 Summary {
37 count: self.count + other.count,
38 total_cents: self.total_cents + other.total_cents,
39 }
40 }
41}
42
43impl Monoid for Summary {
44 fn empty() -> Self {
45 Summary {
46 count: 0,
47 total_cents: 0,
48 }
49 }
50}
51
52fn parse_record(raw: RawRecord) -> Option<Transaction> {
56 let name = raw.name.clone();
57 let category = raw.category.clone();
58 do_! { OptionF;
59 id = raw.id.parse::<u32>().ok();
60 amount = raw.amount.parse::<f64>().ok();
61 Some(Transaction {
62 id,
63 name: name.clone(),
64 amount_cents: (amount * 100.0) as i64,
65 category: category.clone(),
66 })
67 }
68}
69
70fn amount_lens() -> SimpleLens<Transaction, i64> {
73 Lens::new(
74 |t: &Transaction| t.amount_cents,
75 |t, amount_cents| Transaction { amount_cents, ..t },
76 )
77}
78
79fn name_lens() -> SimpleLens<Transaction, String> {
80 Lens::new(
81 |t: &Transaction| t.name.clone(),
82 |t, name| Transaction { name, ..t },
83 )
84}
85
86fn apply_discount(transactions: Vec<Transaction>, pct: f64) -> Vec<Transaction> {
90 let lens = amount_lens();
91 VecF::fmap(transactions, |t| {
92 lens.over(t, |a| (a as f64 * (1.0 - pct / 100.0)) as i64)
93 })
94}
95
96fn normalize_names(transactions: Vec<Transaction>) -> Vec<Transaction> {
98 let lens = name_lens();
99 VecF::fmap(transactions, |t| lens.over(t, |n| n.to_uppercase()))
100}
101
102fn summarize(transactions: &[Transaction]) -> Summary {
106 VecF::fold_map(transactions.to_vec(), |t| Summary {
107 count: 1,
108 total_cents: t.amount_cents,
109 })
110}
111
112fn summarize_by_category(transactions: &[Transaction]) -> Vec<(String, Summary)> {
114 let mut categories: Vec<String> = transactions.iter().map(|t| t.category.clone()).collect();
115 categories.sort();
116 categories.dedup();
117
118 categories
119 .into_iter()
120 .map(|cat| {
121 let filtered: Vec<Transaction> = transactions
122 .iter()
123 .filter(|t| t.category == cat)
124 .cloned()
125 .collect();
126 (cat, summarize(&filtered))
127 })
128 .collect()
129}
130
131fn parse_all(records: Vec<RawRecord>) -> Option<Vec<Transaction>> {
134 VecF::traverse::<OptionF, _, _, _>(records, parse_record)
135}
136
137fn main() {
138 println!("=== Data Transformation Example ===\n");
139
140 let records = vec![
142 RawRecord {
143 id: "1".into(),
144 name: "Alice".into(),
145 amount: "99.99".into(),
146 category: "electronics".into(),
147 },
148 RawRecord {
149 id: "2".into(),
150 name: "Bob".into(),
151 amount: "24.50".into(),
152 category: "books".into(),
153 },
154 RawRecord {
155 id: "3".into(),
156 name: "Carol".into(),
157 amount: "149.00".into(),
158 category: "electronics".into(),
159 },
160 RawRecord {
161 id: "4".into(),
162 name: "Dave".into(),
163 amount: "12.75".into(),
164 category: "books".into(),
165 },
166 ];
167
168 println!("--- Parse records (Traversable) ---");
170 let transactions = parse_all(records).expect("All records should parse");
171 for t in &transactions {
172 println!(
173 " #{}: {} - ${:.2} ({})",
174 t.id,
175 t.name,
176 t.amount_cents as f64 / 100.0,
177 t.category
178 );
179 }
180
181 println!("\n--- Apply 10% discount (Functor + Lens) ---");
183 let discounted = apply_discount(transactions.clone(), 10.0);
184 for t in &discounted {
185 println!(" #{}: ${:.2}", t.id, t.amount_cents as f64 / 100.0);
186 }
187
188 println!("\n--- Normalize names (Functor + Lens) ---");
189 let normalized = normalize_names(transactions.clone());
190 for t in &normalized {
191 println!(" #{}: {}", t.id, t.name);
192 }
193
194 println!("\n--- Overall summary (Foldable + Monoid) ---");
196 let summary = summarize(&transactions);
197 println!(
198 " {} transactions, total: ${:.2}",
199 summary.count,
200 summary.total_cents as f64 / 100.0
201 );
202
203 println!("\n--- By category ---");
204 for (cat, s) in summarize_by_category(&transactions) {
205 println!(
206 " {}: {} transactions, total: ${:.2}",
207 cat,
208 s.count,
209 s.total_cents as f64 / 100.0
210 );
211 }
212
213 println!("\n--- Failed parse (bad data) ---");
215 let bad_records = vec![
216 RawRecord {
217 id: "5".into(),
218 name: "Eve".into(),
219 amount: "50.00".into(),
220 category: "food".into(),
221 },
222 RawRecord {
223 id: "bad".into(),
224 name: "Frank".into(),
225 amount: "30.00".into(),
226 category: "food".into(),
227 },
228 ];
229 println!(" parse_all result: {:?}", parse_all(bad_records));
230}