use karpal_std::prelude::*;
#[derive(Debug, Clone)]
struct RawRecord {
id: String,
name: String,
amount: String,
category: String,
}
#[derive(Debug, Clone)]
struct Transaction {
id: u32,
name: String,
amount_cents: i64,
category: String,
}
#[derive(Debug, Clone)]
struct Summary {
count: i64,
total_cents: i64,
}
impl Semigroup for Summary {
fn combine(self, other: Self) -> Self {
Summary {
count: self.count + other.count,
total_cents: self.total_cents + other.total_cents,
}
}
}
impl Monoid for Summary {
fn empty() -> Self {
Summary {
count: 0,
total_cents: 0,
}
}
}
fn parse_record(raw: RawRecord) -> Option<Transaction> {
let name = raw.name.clone();
let category = raw.category.clone();
do_! { OptionF;
id = raw.id.parse::<u32>().ok();
amount = raw.amount.parse::<f64>().ok();
Some(Transaction {
id,
name: name.clone(),
amount_cents: (amount * 100.0) as i64,
category: category.clone(),
})
}
}
fn amount_lens() -> SimpleLens<Transaction, i64> {
Lens::new(
|t: &Transaction| t.amount_cents,
|t, amount_cents| Transaction { amount_cents, ..t },
)
}
fn name_lens() -> SimpleLens<Transaction, String> {
Lens::new(
|t: &Transaction| t.name.clone(),
|t, name| Transaction { name, ..t },
)
}
fn apply_discount(transactions: Vec<Transaction>, pct: f64) -> Vec<Transaction> {
let lens = amount_lens();
VecF::fmap(transactions, |t| {
lens.over(t, |a| (a as f64 * (1.0 - pct / 100.0)) as i64)
})
}
fn normalize_names(transactions: Vec<Transaction>) -> Vec<Transaction> {
let lens = name_lens();
VecF::fmap(transactions, |t| lens.over(t, |n| n.to_uppercase()))
}
fn summarize(transactions: &[Transaction]) -> Summary {
VecF::fold_map(transactions.to_vec(), |t| Summary {
count: 1,
total_cents: t.amount_cents,
})
}
fn summarize_by_category(transactions: &[Transaction]) -> Vec<(String, Summary)> {
let mut categories: Vec<String> = transactions.iter().map(|t| t.category.clone()).collect();
categories.sort();
categories.dedup();
categories
.into_iter()
.map(|cat| {
let filtered: Vec<Transaction> = transactions
.iter()
.filter(|t| t.category == cat)
.cloned()
.collect();
(cat, summarize(&filtered))
})
.collect()
}
fn parse_all(records: Vec<RawRecord>) -> Option<Vec<Transaction>> {
VecF::traverse::<OptionF, _, _, _>(records, parse_record)
}
fn main() {
println!("=== Data Transformation Example ===\n");
let records = vec![
RawRecord {
id: "1".into(),
name: "Alice".into(),
amount: "99.99".into(),
category: "electronics".into(),
},
RawRecord {
id: "2".into(),
name: "Bob".into(),
amount: "24.50".into(),
category: "books".into(),
},
RawRecord {
id: "3".into(),
name: "Carol".into(),
amount: "149.00".into(),
category: "electronics".into(),
},
RawRecord {
id: "4".into(),
name: "Dave".into(),
amount: "12.75".into(),
category: "books".into(),
},
];
println!("--- Parse records (Traversable) ---");
let transactions = parse_all(records).expect("All records should parse");
for t in &transactions {
println!(
" #{}: {} - ${:.2} ({})",
t.id,
t.name,
t.amount_cents as f64 / 100.0,
t.category
);
}
println!("\n--- Apply 10% discount (Functor + Lens) ---");
let discounted = apply_discount(transactions.clone(), 10.0);
for t in &discounted {
println!(" #{}: ${:.2}", t.id, t.amount_cents as f64 / 100.0);
}
println!("\n--- Normalize names (Functor + Lens) ---");
let normalized = normalize_names(transactions.clone());
for t in &normalized {
println!(" #{}: {}", t.id, t.name);
}
println!("\n--- Overall summary (Foldable + Monoid) ---");
let summary = summarize(&transactions);
println!(
" {} transactions, total: ${:.2}",
summary.count,
summary.total_cents as f64 / 100.0
);
println!("\n--- By category ---");
for (cat, s) in summarize_by_category(&transactions) {
println!(
" {}: {} transactions, total: ${:.2}",
cat,
s.count,
s.total_cents as f64 / 100.0
);
}
println!("\n--- Failed parse (bad data) ---");
let bad_records = vec![
RawRecord {
id: "5".into(),
name: "Eve".into(),
amount: "50.00".into(),
category: "food".into(),
},
RawRecord {
id: "bad".into(),
name: "Frank".into(),
amount: "30.00".into(),
category: "food".into(),
},
];
println!(" parse_all result: {:?}", parse_all(bad_records));
}