Skip to main content

corophage/
program.rs

1use std::future::Future;
2use std::marker::PhantomData;
3use std::ops::Add;
4
5use frunk_core::coproduct::{CNil, CoproductSubsetter};
6use frunk_core::hlist::{HCons, HNil};
7
8use crate::control::Cancelled;
9use crate::coproduct::{AsyncHandleMut, AsyncHandleWith, HandleMut, HandleWith, HandlersToEffects};
10use crate::coroutine::{Co, CoSend, GenericCo, Yielder};
11use crate::effect::{CanStart, Effects, Resumes};
12use crate::locality::{Local, Locality, Sendable};
13
14/// An effectful computation with no handlers attached.
15///
16/// This is a convenience alias for [`Program`] where all effects in `Effs`
17/// still need to be handled. Use this as the return type of functions that
18/// create programs:
19///
20/// ```ignore
21/// use corophage::prelude::*;
22///
23/// type MyEffs = Effects![Counter, Ask];
24///
25/// fn my_computation<'a>() -> Effectful<'a, MyEffs, String> {
26///     Program::new(|y: Yielder<'_, MyEffs>| async move {
27///         // ...
28///         "result".to_string()
29///     })
30/// }
31/// ```
32pub type Effectful<'a, Effs, R, L = Local> = Program<'a, Effs, R, L, Effs, HNil>;
33
34/// Deprecated alias for [`Effectful`].
35#[deprecated(since = "0.3.0", note = "renamed to `Effectful`")]
36pub type Eff<'a, Effs, R, L = Local> = Effectful<'a, Effs, R, L>;
37
38/// A computation with incrementally attached effect handlers.
39///
40/// Handlers are added one at a time via [`handle`](Program::handle),
41/// or in bulk via [`handle_all`](Program::handle_all).
42/// Once all effects are handled (`Remaining = CNil`), the computation
43/// can be executed via [`run`](Program::run) or [`run_sync`](Program::run_sync).
44pub struct Program<'a, Effs: Effects<'a>, Result, L: Locality, Remaining, Handlers> {
45    pub(crate) co: GenericCo<'a, Effs, Result, L>,
46    handlers: Handlers,
47    _remaining: PhantomData<Remaining>,
48}
49
50impl<'a, Result> Program<'a, CNil, Result, Local, CNil, HNil> {
51    /// Create a new program from a computation closure.
52    ///
53    /// The effect set can be inferred from the closure parameter annotation,
54    /// or specified explicitly via turbofish:
55    ///
56    /// ```ignore
57    /// // With closure annotation:
58    /// Program::new(|y: Yielder<'_, Effs>| async move { ... })
59    ///
60    /// // With turbofish:
61    /// Program::new::<Effects![Counter, Ask], _>(|y| async move { ... })
62    /// ```
63    pub fn new<Effs, F>(
64        f: impl FnOnce(Yielder<'a, Effs>) -> F + 'a,
65    ) -> Program<'a, Effs, Result, Local, Effs, HNil>
66    where
67        Effs: Effects<'a>,
68        F: Future<Output = Result>,
69    {
70        Program::from_co(Co::new(f))
71    }
72}
73
74impl<'a, Result> Program<'a, CNil, Result, Sendable, CNil, HNil> {
75    /// Create a new `Send`-able program from a computation closure.
76    ///
77    /// The effect set can be inferred from the closure parameter annotation,
78    /// or specified explicitly via turbofish:
79    ///
80    /// ```ignore
81    /// // With closure annotation:
82    /// Program::new_send(|y: Yielder<'_, Effs>| async move { ... })
83    ///
84    /// // With turbofish:
85    /// Program::new_send::<Effects![Counter, Ask], _>(|y| async move { ... })
86    /// ```
87    pub fn new_send<Effs, F>(
88        f: impl FnOnce(Yielder<'a, Effs>) -> F + Send + 'a,
89    ) -> Program<'a, Effs, Result, Sendable, Effs, HNil>
90    where
91        Effs: Effects<'a>,
92        for<'r> Resumes<'r, CanStart<Effs>>: Send + Sync,
93        F: Future<Output = Result> + Send,
94    {
95        Program::from_co(CoSend::new(f))
96    }
97}
98
99impl<'a, Effs, R, L> Program<'a, Effs, R, L, Effs, HNil>
100where
101    Effs: Effects<'a>,
102    L: Locality,
103{
104    /// Create a program from an existing coroutine.
105    #[inline]
106    pub fn from_co(co: GenericCo<'a, Effs, R, L>) -> Self {
107        Program {
108            co,
109            handlers: HNil,
110            _remaining: PhantomData,
111        }
112    }
113}
114
115type HandleEffects<'a, Remaining, H, Effs, HandleIdx, SubsetIdx> =
116    <Remaining as CoproductSubsetter<
117        <H as HandlersToEffects<'a, Effs, HandleIdx>>::Effects,
118        SubsetIdx,
119    >>::Remainder;
120
121impl<'a, Effs, R, L, Remaining, Handlers> Program<'a, Effs, R, L, Remaining, Handlers>
122where
123    Effs: Effects<'a>,
124    L: Locality,
125{
126    /// Attach a handler for an unhandled effect.
127    ///
128    /// Handlers can be attached in any order — the effect type is
129    /// inferred from the closure signature and removed from the
130    /// remaining set via [`CoproductSubsetter`].
131    #[allow(clippy::type_complexity)]
132    #[inline]
133    pub fn handle<F, HandleIdx, SubsetIdx>(
134        self,
135        handler: F,
136    ) -> Program<
137        'a,
138        Effs,
139        R,
140        L,
141        HandleEffects<'a, Remaining, HCons<F, HNil>, Effs, HandleIdx, SubsetIdx>,
142        <Handlers as Add<HCons<F, HNil>>>::Output,
143    >
144    where
145        HCons<F, HNil>: HandlersToEffects<'a, Effs, HandleIdx>,
146        Remaining: CoproductSubsetter<
147                <HCons<F, HNil> as HandlersToEffects<'a, Effs, HandleIdx>>::Effects,
148                SubsetIdx,
149            >,
150        Handlers: Add<HCons<F, HNil>>,
151    {
152        self.handle_all(HCons {
153            head: handler,
154            tail: HNil,
155        })
156    }
157
158    /// Attach multiple handlers at once from an HList.
159    ///
160    /// The handlers can be for any subset of the remaining effects,
161    /// in any order. The effect types are inferred from the handler
162    /// closure signatures, and `CoproductSubsetter` removes those
163    /// effects from the `Remaining` set.
164    #[allow(clippy::type_complexity)]
165    #[inline]
166    pub fn handle_all<H, HandleIdx, SubsetIdx>(
167        self,
168        handlers: H,
169    ) -> Program<
170        'a,
171        Effs,
172        R,
173        L,
174        HandleEffects<'a, Remaining, H, Effs, HandleIdx, SubsetIdx>,
175        <Handlers as Add<H>>::Output,
176    >
177    where
178        H: HandlersToEffects<'a, Effs, HandleIdx>,
179        Remaining: CoproductSubsetter<H::Effects, SubsetIdx>,
180        Handlers: Add<H>,
181    {
182        Program {
183            co: self.co,
184            handlers: self.handlers + handlers,
185            _remaining: PhantomData,
186        }
187    }
188}
189
190impl<'a, Effs, R, L, Handlers> Program<'a, Effs, R, L, CNil, Handlers>
191where
192    Effs: Effects<'a>,
193    L: Locality,
194{
195    /// Run the computation synchronously.
196    #[inline]
197    pub fn run_sync<Indices>(self) -> Result<R, Cancelled>
198    where
199        Effs: HandleMut<'a, Effs, Handlers, Indices>,
200    {
201        let mut handlers = self.handlers;
202        crate::sync::run(self.co, &mut handlers)
203    }
204
205    /// Run the computation synchronously with shared state.
206    #[inline]
207    pub fn run_sync_stateful<S, Indices>(self, state: &mut S) -> Result<R, Cancelled>
208    where
209        Effs: HandleWith<'a, Effs, Handlers, S, Indices>,
210    {
211        let handlers = self.handlers;
212        crate::sync::run_stateful(self.co, state, &handlers)
213    }
214
215    /// Run the computation asynchronously.
216    #[inline]
217    pub async fn run<Indices>(self) -> Result<R, Cancelled>
218    where
219        Effs: AsyncHandleMut<'a, Effs, Handlers, Indices>,
220    {
221        let mut handlers = self.handlers;
222        crate::asynk::run(self.co, &mut handlers).await
223    }
224
225    /// Run the computation asynchronously with shared state.
226    #[inline]
227    pub async fn run_stateful<S, Indices>(self, state: &mut S) -> Result<R, Cancelled>
228    where
229        Effs: AsyncHandleWith<'a, Effs, Handlers, S, Indices>,
230    {
231        let handlers = self.handlers;
232        crate::asynk::run_stateful(self.co, state, &handlers).await
233    }
234}