use crate::context::{Cons, Context, Nil, Tagged};
pub trait NeedsInterface<I: EffectInterface> {
fn get_interface(&self) -> &I;
}
impl<I, Tail> NeedsInterface<I> for Context<Cons<Tagged<I::Key, I>, Tail>>
where
I: EffectInterface,
I::Key: 'static,
Tail: 'static,
{
#[inline]
fn get_interface(&self) -> &I {
&self.0.0.value
}
}
pub trait EffectInterface: Sized + 'static {
type Key: 'static;
}
#[inline]
pub fn handler<I, F>(f: F) -> HandlerLayer<I, F>
where
I: EffectInterface,
F: Fn() -> I,
{
HandlerLayer {
f,
_pd: std::marker::PhantomData,
}
}
pub struct HandlerLayer<I, F> {
f: F,
_pd: std::marker::PhantomData<fn() -> I>,
}
impl<I, F> crate::layer::Layer for HandlerLayer<I, F>
where
I: EffectInterface,
F: Fn() -> I,
{
type Output = Tagged<I::Key, I>;
type Error = crate::runtime::Never;
fn build(&self) -> Result<Self::Output, Self::Error> {
Ok(Tagged::<I::Key, I>::new((self.f)()))
}
}
#[inline]
pub fn single_context<I, F>(f: F) -> Context<Cons<Tagged<I::Key, I>, Nil>>
where
I: EffectInterface,
F: Fn() -> I,
{
Context::new(Cons(Tagged::<I::Key, I>::new(f()), Nil))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layer::Layer as _;
use crate::runtime::run_blocking;
crate::service_key!(struct CounterKey);
struct Counter {
increment: Box<dyn Fn(i32) -> i32 + Send + Sync>,
decrement: Box<dyn Fn(i32) -> i32 + Send + Sync>,
}
impl EffectInterface for Counter {
type Key = CounterKey;
}
fn counter_add1() -> Counter {
Counter {
increment: Box::new(|n| n + 1),
decrement: Box::new(|n| n - 1),
}
}
fn counter_mul2() -> Counter {
Counter {
increment: Box::new(|n| n * 2),
decrement: Box::new(|n| n / 2),
}
}
type CounterCtx = Context<Cons<Tagged<CounterKey, Counter>, Nil>>;
mod handler_layer {
use super::*;
#[test]
fn build_returns_tagged_cell() {
let layer = handler(counter_add1);
let cell = layer.build().expect("infallible");
assert_eq!((cell.value.increment)(10), 11);
}
#[test]
fn alternate_handler_swaps_semantics() {
let layer = handler(counter_mul2);
let cell = layer.build().expect("infallible");
assert_eq!((cell.value.increment)(10), 20);
}
}
mod single_context_builder {
use super::*;
#[test]
fn builds_context_with_one_interface() {
let ctx = single_context(counter_add1);
let counter: &Counter = ctx.get::<CounterKey>();
assert_eq!((counter.increment)(5), 6);
}
}
mod calling_code {
use super::*;
use crate::Effect;
fn do_increment(n: i32) -> Effect<i32, (), CounterCtx> {
Effect::new(move |r: &mut CounterCtx| {
let counter: &Counter = r.get::<CounterKey>();
Ok((counter.increment)(n))
})
}
fn do_decrement(n: i32) -> Effect<i32, (), CounterCtx> {
Effect::new(move |r: &mut CounterCtx| {
let counter: &Counter = r.get::<CounterKey>();
Ok((counter.decrement)(n))
})
}
#[test]
fn add1_handler_increment() {
let ctx = single_context(counter_add1);
assert_eq!(run_blocking(do_increment(41), ctx), Ok(42));
}
#[test]
fn mul2_handler_increment() {
let ctx = single_context(counter_mul2);
assert_eq!(run_blocking(do_increment(21), ctx), Ok(42));
}
#[test]
fn handler_swap_is_transparent_to_callers() {
let ctx_add = single_context(counter_add1);
let ctx_mul = single_context(counter_mul2);
let r1 = run_blocking(do_decrement(43), ctx_add); let r2 = run_blocking(do_decrement(84), ctx_mul);
assert_eq!(r1, Ok(42));
assert_eq!(r2, Ok(42));
}
#[test]
fn operations_compose_through_flat_map() {
let ctx = single_context(counter_add1);
let composed = do_increment(0)
.flat_map(|n| do_increment(n))
.flat_map(|n| do_increment(n));
assert_eq!(run_blocking(composed, ctx), Ok(3)); }
}
mod interface_identity_law {
use super::*;
#[test]
fn handler_layer_equivalent_to_manual_tagged() {
let via_layer = handler(counter_add1).build().expect("infallible");
let manual = Tagged::<CounterKey, _>::new(counter_add1());
assert_eq!(
(via_layer.value.increment)(10),
(manual.value.increment)(10)
);
}
#[test]
fn get_interface_returns_interface_from_context() {
let ctx = single_context(counter_add1);
let counter: &Counter = ctx.get_interface();
assert_eq!((counter.increment)(10), 11);
}
}
}