use stillwater::effect::prelude::*;
async fn example_basic_effects() {
println!("\n=== Example 1: Basic Effects ===");
let success_effect = pure::<_, String, ()>(42);
let result = success_effect.execute(&()).await;
println!("Pure effect: {:?}", result);
let fail_effect = fail::<i32, _, ()>("something went wrong".to_string());
let result = fail_effect.execute(&()).await;
println!("Fail effect: {:?}", result);
}
async fn example_from_fn() {
println!("\n=== Example 2: Effects from Functions ===");
#[derive(Clone)]
struct Env {
multiplier: i32,
}
let effect = from_fn(|env: &Env| Ok::<_, String>(env.multiplier * 2));
let env = Env { multiplier: 21 };
let result = effect.execute(&env).await;
println!("Result: {:?}", result);
}
async fn example_mapping() {
println!("\n=== Example 3: Mapping Effects ===");
#[derive(Clone)]
struct Env {
base_value: i32,
}
let effect = from_fn(|env: &Env| Ok::<_, String>(env.base_value))
.map(|x| x * 2) .map(|x| x + 10) .map(|x| format!("Result: {}", x));
let env = Env { base_value: 5 };
let result = effect.execute(&env).await.unwrap();
println!("{}", result); }
async fn example_chaining() {
println!("\n=== Example 4: Chaining Effects ===");
#[derive(Clone)]
struct Database {
value: i32,
}
#[derive(Clone)]
struct Env {
db: Database,
}
impl AsRef<Database> for Env {
fn as_ref(&self) -> &Database {
&self.db
}
}
fn get_value() -> impl Effect<Output = i32, Error = String, Env = Env> {
from_fn(|env: &Env| Ok::<_, String>(env.db.value))
}
fn validate_and_double(x: i32) -> impl Effect<Output = i32, Error = String, Env = Env> {
from_fn(move |_: &Env| {
if x > 0 {
Ok(x * 2)
} else {
Err("Value must be positive".to_string())
}
})
}
let env = Env {
db: Database { value: 10 },
};
let result = get_value()
.and_then(validate_and_double)
.execute(&env)
.await;
println!("Success case: {:?}", result);
let env2 = Env {
db: Database { value: -5 },
};
let result2 = get_value()
.and_then(validate_and_double)
.execute(&env2)
.await;
println!("Failure case: {:?}", result2);
}
async fn example_error_handling() {
println!("\n=== Example 5: Error Handling ===");
#[derive(Clone)]
struct Env {
value: i32,
}
let _effect = from_fn(|env: &Env| {
if env.value > 0 {
Ok::<_, &str>(env.value)
} else {
Err("negative")
}
})
.map_err(|e| format!("Error: {} is not allowed", e));
let env1 = Env { value: 42 };
let effect1 = from_fn(|env: &Env| {
if env.value > 0 {
Ok::<_, &str>(env.value)
} else {
Err("negative")
}
})
.map_err(|e| format!("Error: {} is not allowed", e));
println!("Valid value: {:?}", effect1.execute(&env1).await);
let env2 = Env { value: -1 };
let effect2 = from_fn(|env: &Env| {
if env.value > 0 {
Ok::<_, &str>(env.value)
} else {
Err("negative")
}
})
.map_err(|e| format!("Error: {} is not allowed", e));
println!("Invalid value: {:?}", effect2.execute(&env2).await);
}
async fn example_async_effects() {
println!("\n=== Example 6: Async Effects ===");
#[derive(Clone)]
struct ApiClient {
base_url: String,
}
#[derive(Clone)]
struct Env {
api: ApiClient,
}
impl AsRef<ApiClient> for Env {
fn as_ref(&self) -> &ApiClient {
&self.api
}
}
let env = Env {
api: ApiClient {
base_url: "https://api.example.com".to_string(),
},
};
let fetch_user = from_async(|env: &Env| {
let url = env.api.base_url.clone();
async move {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
Ok::<_, String>(format!("User from {}", url))
}
});
let result = fetch_user.execute(&env).await.unwrap();
println!("Fetched: {}", result);
}
async fn example_tap() {
println!("\n=== Example 7: Using tap() ===");
#[derive(Clone)]
struct Env {
value: i32,
}
let effect = from_fn(|env: &Env| Ok::<_, String>(env.value))
.tap(|x| {
println!(" [DEBUG] Got value: {}", x);
pure::<(), String, Env>(())
})
.map(|x| x * 2)
.tap(|x| {
println!(" [DEBUG] After doubling: {}", x);
pure::<(), String, Env>(())
})
.map(|x| x + 5);
let env = Env { value: 10 };
let result = effect.execute(&env).await.unwrap();
println!("Final result: {}", result);
}
async fn example_check() {
println!("\n=== Example 8: Using check() ===");
#[derive(Clone)]
struct Env {
age: i32,
}
let env1 = Env { age: 25 };
let result1 = from_fn(|env: &Env| Ok::<_, String>(env.age))
.and_then(|age| {
from_fn(move |_: &Env| {
if age >= 18 {
Ok(age)
} else {
Err(format!("Age {} is below minimum (18)", age))
}
})
})
.execute(&env1)
.await;
println!("Adult: {:?}", result1);
let env2 = Env { age: 16 };
let result2 = from_fn(|env: &Env| Ok::<_, String>(env.age))
.and_then(|age| {
from_fn(move |_: &Env| {
if age >= 18 {
Ok(age)
} else {
Err(format!("Age {} is below minimum (18)", age))
}
})
})
.execute(&env2)
.await;
println!("Minor: {:?}", result2);
}
async fn example_with() {
println!("\n=== Example 9: Using with() ===");
#[derive(Clone)]
struct Config {
width: i32,
height: i32,
}
#[derive(Clone)]
struct Env {
config: Config,
}
impl AsRef<Config> for Env {
fn as_ref(&self) -> &Config {
&self.config
}
}
let area_effect = from_fn(|env: &Env| Ok::<_, String>(env.config.width))
.with(|_w| from_fn(|env: &Env| Ok::<_, String>(env.config.height)))
.map(|(w, h)| w * h);
let env = Env {
config: Config {
width: 10,
height: 5,
},
};
let area = area_effect.execute(&env).await.unwrap();
println!("Area: {}", area);
}
async fn example_zip() {
println!("\n=== Example 10: Using zip() ===");
#[derive(Clone, Debug)]
struct User {
name: String,
}
#[derive(Clone, Debug)]
struct Settings {
theme: String,
}
#[derive(Clone)]
struct Env {
user_name: String,
theme: String,
}
fn fetch_user() -> impl Effect<Output = User, Error = String, Env = Env> {
from_fn(|env: &Env| {
Ok(User {
name: env.user_name.clone(),
})
})
}
fn fetch_settings() -> impl Effect<Output = Settings, Error = String, Env = Env> {
from_fn(|env: &Env| {
Ok(Settings {
theme: env.theme.clone(),
})
})
}
let env = Env {
user_name: "Alice".to_string(),
theme: "dark".to_string(),
};
let result = fetch_user().zip(fetch_settings()).execute(&env).await;
println!("Basic zip: {:?}", result);
let greeting = fetch_user()
.zip_with(fetch_settings(), |user, settings| {
format!("Hello {}, your theme is {}", user.name, settings.theme)
})
.execute(&env)
.await;
println!("zip_with: {:?}", greeting);
let chained = pure::<_, String, Env>(1)
.zip(pure(2))
.zip(pure(3))
.map(|((a, b), c)| a + b + c)
.execute(&env)
.await;
println!("Chained zips ((a, b), c): {:?}", chained);
let flat = zip3(
pure::<_, String, Env>(1),
pure::<_, String, Env>(2),
pure::<_, String, Env>(3),
)
.map(|(a, b, c)| a + b + c)
.execute(&env)
.await;
println!("zip3 (a, b, c): {:?}", flat);
let with_error = pure::<_, String, Env>(1)
.zip(fail::<i32, _, Env>("second failed".to_string()))
.execute(&env)
.await;
println!("With error: {:?}", with_error);
}
async fn example_composition() {
println!("\n=== Example 11: Real-world Composition ===");
#[derive(Clone)]
struct User {
id: u64,
name: String,
age: i32,
}
#[derive(Clone)]
struct Database {
users: Vec<User>,
}
#[derive(Clone)]
struct Env {
db: Database,
}
impl AsRef<Database> for Env {
fn as_ref(&self) -> &Database {
&self.db
}
}
fn find_user(user_id: u64) -> impl Effect<Output = User, Error = String, Env = Env> {
from_fn(move |env: &Env| {
env.db
.users
.iter()
.find(|u| u.id == user_id)
.cloned()
.ok_or_else(|| format!("User {} not found", user_id))
})
}
fn validate_adult(user: User) -> impl Effect<Output = User, Error = String, Env = Env> {
from_fn(move |_: &Env| {
if user.age >= 18 {
Ok(user.clone())
} else {
Err(format!("User {} is not an adult", user.name))
}
})
}
fn greet(user: User) -> impl Effect<Output = String, Error = String, Env = Env> {
pure(format!("Hello, {}!", user.name))
}
let workflow = find_user(1)
.tap(|u| {
println!(" Found user: {}", u.name);
pure::<(), String, Env>(())
})
.and_then(validate_adult)
.tap(|u| {
println!(" Validated user: {}", u.name);
pure::<(), String, Env>(())
})
.and_then(greet);
let env = Env {
db: Database {
users: vec![
User {
id: 1,
name: "Alice".to_string(),
age: 25,
},
User {
id: 2,
name: "Bob".to_string(),
age: 16,
},
],
},
};
match workflow.execute(&env).await {
Ok(greeting) => println!("Success: {}", greeting),
Err(e) => println!("Error: {}", e),
}
let workflow2 = find_user(2).and_then(validate_adult).and_then(greet);
match workflow2.execute(&env).await {
Ok(greeting) => println!("Success: {}", greeting),
Err(e) => println!("Error: {}", e),
}
}
#[tokio::main]
async fn main() {
println!("Effects Examples");
println!("================");
example_basic_effects().await;
example_from_fn().await;
example_mapping().await;
example_chaining().await;
example_error_handling().await;
example_async_effects().await;
example_tap().await;
example_check().await;
example_with().await;
example_zip().await;
example_composition().await;
println!("\n=== All examples completed successfully! ===");
}