Skip to main content

tidepool_effect/
dispatch.rs

1use crate::error::EffectError;
2use frunk::{HCons, HNil};
3use tidepool_bridge::{FromCore, ToCore};
4use tidepool_eval::value::Value;
5use tidepool_repr::DataConTable;
6
7/// Shared context passed to effect handlers during dispatch.
8///
9/// Carries the [`DataConTable`] (needed for `FromCore`/`ToCore` conversions) and
10/// an optional user-defined state value `U` that handlers can read.
11pub struct EffectContext<'a, U = ()> {
12    table: &'a DataConTable,
13    user: &'a U,
14}
15
16impl<'a, U> EffectContext<'a, U> {
17    /// Create a new context with a user state value and data constructor table.
18    pub fn with_user(table: &'a DataConTable, user: &'a U) -> Self {
19        Self { table, user }
20    }
21
22    /// Convert a Rust value into a Core `Value` suitable for returning to the JIT.
23    pub fn respond<T: ToCore>(&self, val: T) -> Result<Value, EffectError> {
24        val.to_value(self.table).map_err(EffectError::Bridge)
25    }
26
27    /// Access the data constructor table (for manual `FromCore`/`ToCore` calls).
28    pub fn table(&self) -> &DataConTable {
29        self.table
30    }
31
32    /// Access the user-defined state.
33    pub fn user(&self) -> &U {
34        self.user
35    }
36}
37
38/// Handler for a single effect type.
39///
40/// Implement this trait for each Rust struct that handles one Haskell effect.
41/// `Request` is the `#[derive(FromCore)]` enum mirroring the Haskell GADT.
42///
43/// ```ignore
44/// impl EffectHandler for ConsoleHandler {
45///     type Request = ConsoleReq;
46///     fn handle(&mut self, req: ConsoleReq, cx: &EffectContext) -> Result<Value, EffectError> {
47///         match req {
48///             ConsoleReq::Print(msg) => { println!("{msg}"); cx.respond(()) }
49///         }
50///     }
51/// }
52/// ```
53pub trait EffectHandler<U = ()> {
54    type Request: FromCore;
55    fn handle(
56        &mut self,
57        req: Self::Request,
58        cx: &EffectContext<'_, U>,
59    ) -> Result<Value, EffectError>;
60}
61
62/// Tag-based effect dispatch over an HList of handlers.
63///
64/// The JIT yields `(tag, request)` pairs where `tag` identifies which effect
65/// in the `Eff '[E0, E1, ...]` list fired. `DispatchEffect` peels one layer
66/// per HCons: tag 0 → head handler, tag N → tail with tag Nāˆ’1.
67///
68/// You don't implement this manually — it's derived for `frunk::HList![H0, H1, ...]`
69/// when each `Hi: EffectHandler`.
70pub trait DispatchEffect<U = ()> {
71    fn dispatch(
72        &mut self,
73        tag: u64,
74        request: &Value,
75        cx: &EffectContext<'_, U>,
76    ) -> Result<Value, EffectError>;
77}
78
79impl<U> DispatchEffect<U> for HNil {
80    fn dispatch(
81        &mut self,
82        tag: u64,
83        _request: &Value,
84        _cx: &EffectContext<'_, U>,
85    ) -> Result<Value, EffectError> {
86        Err(EffectError::UnhandledEffect { tag })
87    }
88}
89
90impl<U, H: EffectHandler<U>, T: DispatchEffect<U>> DispatchEffect<U> for HCons<H, T> {
91    fn dispatch(
92        &mut self,
93        tag: u64,
94        request: &Value,
95        cx: &EffectContext<'_, U>,
96    ) -> Result<Value, EffectError> {
97        if tag == 0 {
98            let req = H::Request::from_value(request, cx.table())?;
99            self.head.handle(req, cx)
100        } else {
101            self.tail.dispatch(tag - 1, request, cx)
102        }
103    }
104}