use crate::errors::AnalysisError;
use stillwater::effect::prelude::*;
use stillwater::{BoxedEffect, Effect, EffectExt};
pub fn traverse_effect<T, U, Env, F, Eff>(
items: Vec<T>,
f: F,
) -> BoxedEffect<Vec<U>, AnalysisError, Env>
where
T: Send + 'static,
U: Send + 'static,
Env: Clone + Send + Sync + 'static,
F: Fn(T) -> Eff + Send + Sync + 'static,
Eff: Effect<Output = U, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let mut results = Vec::with_capacity(items.len());
for item in items {
let result = f(item).run(&env).await?;
results.push(result);
}
Ok(results)
}
})
.boxed()
}
pub fn par_traverse_effect<T, U, Env, F, Eff>(
items: Vec<T>,
f: F,
) -> BoxedEffect<Vec<U>, AnalysisError, Env>
where
T: Send + 'static,
U: Send + 'static,
Env: Clone + Send + Sync + 'static,
F: Fn(T) -> Eff + Send + Sync + Clone + 'static,
Eff: Effect<Output = U, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
let f = f.clone();
async move {
let mut results = Vec::with_capacity(items.len());
for item in items {
let result = f(item).run(&env).await?;
results.push(result);
}
Ok(results)
}
})
.boxed()
}
pub fn filter_effect<T, Env, F, Eff>(
items: Vec<T>,
predicate: F,
) -> BoxedEffect<Vec<T>, AnalysisError, Env>
where
T: Send + Clone + 'static,
Env: Clone + Send + Sync + 'static,
F: Fn(&T) -> Eff + Send + Sync + 'static,
Eff: Effect<Output = bool, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let mut results = Vec::new();
for item in items {
let keep = predicate(&item).run(&env).await?;
if keep {
results.push(item);
}
}
Ok(results)
}
})
.boxed()
}
pub fn fold_effect<T, A, Env, F, Eff>(
items: Vec<T>,
init: A,
f: F,
) -> BoxedEffect<A, AnalysisError, Env>
where
T: Send + 'static,
A: Send + Clone + 'static,
Env: Clone + Send + Sync + 'static,
F: Fn(A, T) -> Eff + Send + Sync + 'static,
Eff: Effect<Output = A, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let mut acc = init;
for item in items {
acc = f(acc.clone(), item).run(&env).await?;
}
Ok(acc)
}
})
.boxed()
}
pub fn filter_map_effect<T, U, Env, F, Eff>(
items: Vec<T>,
f: F,
) -> BoxedEffect<Vec<U>, AnalysisError, Env>
where
T: Send + 'static,
U: Send + 'static,
Env: Clone + Send + Sync + 'static,
F: Fn(T) -> Eff + Send + Sync + 'static,
Eff: Effect<Output = Option<U>, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let mut results = Vec::new();
for item in items {
if let Some(value) = f(item).run(&env).await? {
results.push(value);
}
}
Ok(results)
}
})
.boxed()
}
pub fn sequence_effects<T, Env>(
effects: Vec<BoxedEffect<T, AnalysisError, Env>>,
) -> BoxedEffect<Vec<T>, AnalysisError, Env>
where
T: Send + 'static,
Env: Clone + Send + Sync + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
let effects = effects;
async move {
let mut results = Vec::with_capacity(effects.len());
for effect in effects {
let result = effect.run(&env).await?;
results.push(result);
}
Ok(results)
}
})
.boxed()
}
pub fn first<A, B, Env, E1, E2>(e1: E1, e2: E2) -> BoxedEffect<A, AnalysisError, Env>
where
A: Send + 'static,
B: Send + 'static,
Env: Clone + Send + Sync + 'static,
E1: Effect<Output = A, Error = AnalysisError, Env = Env> + Send + 'static,
E2: Effect<Output = B, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let a = e1.run(&env).await?;
let _ = e2.run(&env).await?;
Ok(a)
}
})
.boxed()
}
pub fn second<A, B, Env, E1, E2>(e1: E1, e2: E2) -> BoxedEffect<B, AnalysisError, Env>
where
A: Send + 'static,
B: Send + 'static,
Env: Clone + Send + Sync + 'static,
E1: Effect<Output = A, Error = AnalysisError, Env = Env> + Send + 'static,
E2: Effect<Output = B, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let _ = e1.run(&env).await?;
let b = e2.run(&env).await?;
Ok(b)
}
})
.boxed()
}
pub fn zip_effect<A, B, Env, E1, E2>(e1: E1, e2: E2) -> BoxedEffect<(A, B), AnalysisError, Env>
where
A: Send + 'static,
B: Send + 'static,
Env: Clone + Send + Sync + 'static,
E1: Effect<Output = A, Error = AnalysisError, Env = Env> + Send + 'static,
E2: Effect<Output = B, Error = AnalysisError, Env = Env> + Send + 'static,
{
from_async(move |env: &Env| {
let env = env.clone();
async move {
let a = e1.run(&env).await?;
let b = e2.run(&env).await?;
Ok((a, b))
}
})
.boxed()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::DebtmapConfig;
use crate::env::RealEnv;
fn test_env() -> RealEnv {
RealEnv::new(DebtmapConfig::default())
}
#[tokio::test]
async fn test_traverse_effect_empty() {
let env = test_env();
let items: Vec<i32> = vec![];
let effect = traverse_effect(items, |n| pure::<_, AnalysisError, RealEnv>(n * 2));
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), Vec::<i32>::new());
}
#[tokio::test]
async fn test_traverse_effect_success() {
let env = test_env();
let items = vec![1, 2, 3, 4, 5];
let effect = traverse_effect(items, |n| pure::<_, AnalysisError, RealEnv>(n * 2));
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![2, 4, 6, 8, 10]);
}
#[tokio::test]
async fn test_traverse_effect_stops_on_error() {
let env = test_env();
let items = vec![1, 2, 3, 4, 5];
let effect = traverse_effect(items, |n| {
if n == 3 {
fail::<i32, AnalysisError, RealEnv>(AnalysisError::other("error at 3")).boxed()
} else {
pure::<_, AnalysisError, RealEnv>(n * 2).boxed()
}
});
let result = effect.run(&env).await;
assert!(result.is_err());
assert!(result.unwrap_err().message().contains("error at 3"));
}
#[tokio::test]
async fn test_par_traverse_effect() {
let env = test_env();
let items = vec![1, 2, 3];
let effect = par_traverse_effect(items, |n| pure::<_, AnalysisError, RealEnv>(n * 2));
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![2, 4, 6]);
}
#[tokio::test]
async fn test_filter_effect_keeps_matching() {
let env = test_env();
let items = vec![1, 2, 3, 4, 5];
let effect = filter_effect(items, |n| pure::<_, AnalysisError, RealEnv>(n % 2 == 0));
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![2, 4]);
}
#[tokio::test]
async fn test_filter_effect_empty() {
let env = test_env();
let items = vec![1, 3, 5];
let effect = filter_effect(items, |n| pure::<_, AnalysisError, RealEnv>(n % 2 == 0));
let result = effect.run(&env).await;
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[tokio::test]
async fn test_fold_effect() {
let env = test_env();
let items = vec![1, 2, 3, 4, 5];
let effect = fold_effect(items, 0, |acc, n| {
pure::<_, AnalysisError, RealEnv>(acc + n)
});
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 15);
}
#[tokio::test]
async fn test_fold_effect_empty() {
let env = test_env();
let items: Vec<i32> = vec![];
let effect = fold_effect(items, 42, |acc, n| {
pure::<_, AnalysisError, RealEnv>(acc + n)
});
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
#[tokio::test]
async fn test_filter_map_effect() {
let env = test_env();
let items = vec![1, 2, 3, 4, 5];
let effect = filter_map_effect(items, |n| {
if n % 2 == 0 {
pure::<_, AnalysisError, RealEnv>(Some(n * 10))
} else {
pure::<_, AnalysisError, RealEnv>(None)
}
});
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![20, 40]);
}
#[tokio::test]
async fn test_sequence_effects() {
let env = test_env();
let effects: Vec<BoxedEffect<i32, AnalysisError, RealEnv>> =
vec![pure(1).boxed(), pure(2).boxed(), pure(3).boxed()];
let effect = sequence_effects(effects);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![1, 2, 3]);
}
#[tokio::test]
async fn test_first_combinator() {
let env = test_env();
let effect = first(
pure::<_, AnalysisError, RealEnv>("first"),
pure::<_, AnalysisError, RealEnv>("second"),
);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "first");
}
#[tokio::test]
async fn test_second_combinator() {
let env = test_env();
let effect = second(
pure::<_, AnalysisError, RealEnv>("first"),
pure::<_, AnalysisError, RealEnv>("second"),
);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "second");
}
#[tokio::test]
async fn test_zip_effect() {
let env = test_env();
let effect = zip_effect(
pure::<_, AnalysisError, RealEnv>(1),
pure::<_, AnalysisError, RealEnv>("hello"),
);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), (1, "hello"));
}
}