use crate::foundation::coproduct::Either;
pub trait Selective {
type Left;
type Right;
type Output;
fn select(self, f: Self::Output) -> Self::Right;
}
use crate::kernel::effect::{Effect, box_future};
#[inline]
pub fn select_effect<A, B, E, R, F>(
fab: Effect<Either<B, A>, E, R>,
ff: Effect<F, E, R>,
) -> Effect<B, E, R>
where
A: 'static,
B: 'static,
E: 'static,
R: 'static,
F: FnOnce(A) -> B + 'static,
{
Effect::new_async(move |r| {
box_future(async move {
match fab.run(r).await? {
Ok(b) => Ok(b),
Err(a) => {
let f = ff.run(r).await?;
Ok(f(a))
}
}
})
})
}
#[inline]
pub fn branch_effect<A, B, C, E, R, FL, FR>(
fab: Effect<Either<B, A>, E, R>,
fl: Effect<FL, E, R>,
fr: Effect<FR, E, R>,
) -> Effect<C, E, R>
where
A: 'static,
B: 'static,
C: 'static,
E: 'static,
R: 'static,
FL: FnOnce(A) -> C + 'static,
FR: FnOnce(B) -> C + 'static,
{
Effect::new_async(move |r| {
box_future(async move {
match fab.run(r).await? {
Err(a) => {
let f = fl.run(r).await?;
Ok(f(a))
}
Ok(b) => {
let g = fr.run(r).await?;
Ok(g(b))
}
}
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::effect::{fail, succeed};
use crate::runtime::run_blocking;
use rstest::rstest;
fn right_effect(n: i32) -> Effect<Either<i32, i32>, (), ()> {
succeed(Ok(n))
}
fn left_effect(n: i32) -> Effect<Either<i32, i32>, (), ()> {
succeed(Err(n))
}
mod select_right_short_circuits {
use super::*;
#[test]
fn right_returns_value_without_running_ff() {
let executed = std::cell::Cell::new(false);
let result = run_blocking(
select_effect(
right_effect(42),
succeed(|_: i32| {
panic!("ff executed on Right branch")
}),
),
(),
);
assert_eq!(result, Ok(42));
let _ = executed;
}
#[rstest]
#[case::zero(0)]
#[case::positive(99)]
#[case::negative(-7)]
fn right_returns_original_value_unchanged(#[case] n: i32) {
let result = run_blocking(select_effect(right_effect(n), succeed(|_: i32| -1)), ());
assert_eq!(result, Ok(n));
}
}
mod select_left_runs_ff {
use super::*;
#[test]
fn left_applies_function_from_ff() {
let result = run_blocking(select_effect(left_effect(6), succeed(|x: i32| x * 7)), ());
assert_eq!(result, Ok(42));
}
#[rstest]
#[case::double(2, |x: i32| x * 2, 4)]
#[case::negate(5, |x: i32| -x, -5)]
#[case::identity(99, |x: i32| x, 99)]
fn left_applies_various_functions(
#[case] input: i32,
#[case] f: fn(i32) -> i32,
#[case] expected: i32,
) {
let result = run_blocking(select_effect(left_effect(input), succeed(f)), ());
assert_eq!(result, Ok(expected));
}
}
mod select_pure_function_degenerates_to_map {
use super::*;
#[test]
fn pure_ff_equivalent_to_map_either() {
let selective = run_blocking(select_effect(left_effect(21), succeed(|a: i32| a * 2)), ());
let mapped = run_blocking(
left_effect(21).map(|e| match e {
Err(a) => a * 2,
Ok(b) => b,
}),
(),
);
assert_eq!(selective, mapped);
}
}
mod branch {
use super::*;
#[test]
fn branch_left_runs_left_handler_only() {
let result = run_blocking(
branch_effect(
left_effect(6),
succeed(|a: i32| a * 7), succeed(|_: i32| panic!("right executed on left input")),
),
(),
);
assert_eq!(result, Ok(42));
}
#[test]
fn branch_right_runs_right_handler_only() {
let result = run_blocking(
branch_effect(
right_effect(40),
succeed(|_: i32| panic!("left executed on right input")),
succeed(|b: i32| b + 2), ),
(),
);
assert_eq!(result, Ok(42));
}
#[test]
fn branch_propagates_error_from_selected_handler() {
let result: Result<i32, &str> = run_blocking(
branch_effect(
succeed::<Either<i32, i32>, &str, ()>(Err(0)),
fail::<fn(i32) -> i32, &str, ()>("handler failed"),
succeed(|b: i32| b),
),
(),
);
assert_eq!(result, Err("handler failed"));
}
#[test]
fn branch_propagates_error_from_fab() {
let result: Result<i32, &str> = run_blocking(
branch_effect(
fail::<Either<i32, i32>, &str, ()>("fab failed"),
succeed(|a: i32| a),
succeed(|b: i32| b),
),
(),
);
assert_eq!(result, Err("fab failed"));
}
}
mod select_error_propagation {
use super::*;
#[test]
fn select_propagates_error_from_fab() {
let result: Result<i32, &str> = run_blocking(
select_effect(
fail::<Either<i32, i32>, &str, ()>("fab failed"),
succeed(|_: i32| 0),
),
(),
);
assert_eq!(result, Err("fab failed"));
}
#[test]
fn select_propagates_error_from_ff() {
let result: Result<i32, &str> = run_blocking(
select_effect(
succeed::<Either<i32, i32>, &str, ()>(Err(5)),
fail::<fn(i32) -> i32, &str, ()>("ff failed"),
),
(),
);
assert_eq!(result, Err("ff failed"));
}
}
}