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}