use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use stillwater::{from_fn, pure, Effect, EffectExt, IO};
async fn example_basic_read() {
println!("\n=== Example 1: Basic Read Operation ===");
#[derive(Clone)]
struct Database {
users: Vec<String>,
}
impl Database {
fn count_users(&self) -> usize {
self.users.len()
}
}
#[derive(Clone)]
struct Env {
db: Database,
}
impl AsRef<Database> for Env {
fn as_ref(&self) -> &Database {
&self.db
}
}
let env = Env {
db: Database {
users: vec![
"Alice".to_string(),
"Bob".to_string(),
"Charlie".to_string(),
],
},
};
let effect = IO::read(|db: &Database| db.count_users());
let count = effect.run(&env).await.unwrap();
println!("User count: {}", count);
}
async fn example_basic_write() {
println!("\n=== Example 2: Basic Write Operation ===");
#[derive(Clone)]
struct Logger {
messages: Arc<Mutex<Vec<String>>>,
}
impl Logger {
fn log(&self, msg: String) {
self.messages.lock().unwrap().push(msg);
}
fn get_messages(&self) -> Vec<String> {
self.messages.lock().unwrap().clone()
}
}
#[derive(Clone)]
struct Env {
logger: Logger,
}
impl AsRef<Logger> for Env {
fn as_ref(&self) -> &Logger {
&self.logger
}
}
let env = Env {
logger: Logger {
messages: Arc::new(Mutex::new(Vec::new())),
},
};
let effect = IO::write(|logger: &Logger| {
logger.log("Application started".to_string());
logger.log("Processing request".to_string());
});
effect.run(&env).await.unwrap();
println!("Logged messages: {:?}", env.logger.get_messages());
}
async fn example_multiple_services() {
println!("\n=== Example 3: Multiple Services ===");
#[derive(Clone)]
struct Database {
name: String,
}
#[derive(Clone)]
struct Cache {
name: String,
}
#[derive(Clone)]
struct Logger {
name: String,
}
#[derive(Clone)]
struct Env {
db: Database,
cache: Cache,
logger: Logger,
}
impl AsRef<Database> for Env {
fn as_ref(&self) -> &Database {
&self.db
}
}
impl AsRef<Cache> for Env {
fn as_ref(&self) -> &Cache {
&self.cache
}
}
impl AsRef<Logger> for Env {
fn as_ref(&self) -> &Logger {
&self.logger
}
}
let env = Env {
db: Database {
name: "PostgreSQL".to_string(),
},
cache: Cache {
name: "Redis".to_string(),
},
logger: Logger {
name: "Syslog".to_string(),
},
};
let db_effect = IO::read(|db: &Database| db.name.clone());
let cache_effect = IO::read(|cache: &Cache| cache.name.clone());
let logger_effect = IO::read(|logger: &Logger| logger.name.clone());
println!("Database: {}", db_effect.run(&env).await.unwrap());
println!("Cache: {}", cache_effect.run(&env).await.unwrap());
println!("Logger: {}", logger_effect.run(&env).await.unwrap());
}
async fn example_async_operations() {
use std::future::ready;
println!("\n=== Example 4: Async Operations ===");
#[derive(Clone)]
struct ApiClient {
base_url: String,
}
#[derive(Clone)]
struct AuditLog {
entries: Arc<Mutex<Vec<String>>>,
}
impl AuditLog {
fn record_sync(&self, entry: String) {
self.entries.lock().unwrap().push(entry);
}
}
#[derive(Clone)]
struct Env {
api: ApiClient,
audit: AuditLog,
}
impl AsRef<ApiClient> for Env {
fn as_ref(&self) -> &ApiClient {
&self.api
}
}
impl AsRef<AuditLog> for Env {
fn as_ref(&self) -> &AuditLog {
&self.audit
}
}
let env = Env {
api: ApiClient {
base_url: "https://api.example.com".to_string(),
},
audit: AuditLog {
entries: Arc::new(Mutex::new(Vec::new())),
},
};
let fetch_effect = IO::read_async(|api: &ApiClient| {
let base_url = api.base_url.clone();
async move {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
format!("User 42 from {}", base_url)
}
});
let user = fetch_effect.run(&env).await.unwrap();
println!("Fetched: {}", user);
let audit_effect = IO::write_async(|audit: &AuditLog| {
audit.record_sync("User 42 accessed".to_string());
ready(())
});
audit_effect.run(&env).await.unwrap();
println!("Audit log: {:?}", env.audit.entries.lock().unwrap().clone());
}
async fn example_cache_aside() {
println!("\n=== Example 5: Cache-Aside Pattern ===");
#[derive(Clone, Debug, PartialEq)]
struct User {
id: u64,
name: String,
}
#[derive(Clone)]
struct Database {
users: HashMap<u64, User>,
}
#[derive(Clone)]
struct Cache {
data: Arc<Mutex<HashMap<u64, User>>>,
}
impl Cache {
fn get(&self, id: u64) -> Option<User> {
self.data.lock().unwrap().get(&id).cloned()
}
fn set(&self, id: u64, user: User) {
self.data.lock().unwrap().insert(id, user);
}
}
#[derive(Clone)]
struct Env {
db: Database,
cache: Cache,
}
impl AsRef<Database> for Env {
fn as_ref(&self) -> &Database {
&self.db
}
}
impl AsRef<Cache> for Env {
fn as_ref(&self) -> &Cache {
&self.cache
}
}
let mut users = HashMap::new();
users.insert(
1,
User {
id: 1,
name: "Alice".to_string(),
},
);
users.insert(
2,
User {
id: 2,
name: "Bob".to_string(),
},
);
let env = Env {
db: Database { users },
cache: Cache {
data: Arc::new(Mutex::new(HashMap::new())),
},
};
fn get_user(
user_id: u64,
) -> impl EffectExt<Output = Option<User>, Error = std::convert::Infallible, Env = Env> {
IO::read(move |cache: &Cache| cache.get(user_id)).and_then(move |cached| {
match cached {
Some(user) => {
println!("Cache hit for user {}", user_id);
pure(Some(user)).boxed()
}
None => {
println!("Cache miss for user {}", user_id);
IO::read(move |db: &Database| db.users.get(&user_id).cloned())
.and_then(move |user| {
from_fn(move |env: &Env| {
if let Some(ref u) = user {
let user_clone = u.clone();
let cache: &Cache = env.as_ref();
cache.set(user_id, user_clone);
Ok(user.clone())
} else {
Ok(user)
}
})
})
.boxed()
}
}
})
}
let user1 = get_user(1).run(&env).await.unwrap();
println!("Got user: {:?}", user1);
let user1_again = get_user(1).run(&env).await.unwrap();
println!("Got user again: {:?}", user1_again);
let user2 = get_user(2).run(&env).await.unwrap();
println!("Got user: {:?}", user2);
}
async fn example_effect_composition() {
println!("\n=== Example 6: Effect Composition ===");
#[derive(Clone)]
struct Calculator {
multiplier: i32,
}
#[derive(Clone)]
struct Env {
calc: Calculator,
}
impl AsRef<Calculator> for Env {
fn as_ref(&self) -> &Calculator {
&self.calc
}
}
let env = Env {
calc: Calculator { multiplier: 3 },
};
let effect = IO::read(|calc: &Calculator| calc.multiplier)
.map(|x| x * 2) .and_then(|x| {
IO::read(move |calc: &Calculator| x + calc.multiplier)
});
let result = effect.run(&env).await.unwrap();
println!("Result: {} (expected: {})", result, 3 * 2 + 3);
}
#[tokio::main]
async fn main() {
println!("IO Patterns Examples");
println!("====================");
example_basic_read().await;
example_basic_write().await;
example_multiple_services().await;
example_async_operations().await;
example_cache_aside().await;
example_effect_composition().await;
println!("\n=== All examples completed successfully! ===");
}