ordofp_core 0.1.0

OrdoFP core provides developers with HList, Disiunctio, NominataUniversalis, Universalis, and functional type classes
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
//! The Eff Monad - Type-safe effectful computations
//!
//! > *"Effectus in forma monádica"*
//! > — Effects in monadic form. (Neo-Latin)
//!
//! This module provides the `Eff` monad, inspired by Haskell's `effectful` library.
//! `Eff` is a monad that tracks effects at the type level, enabling type-safe
//! composition of effectful computations.
//!
//! # Design
//!
//! The `Eff` monad uses type-level effect lists to track which effects a computation
//! may perform. Handlers eliminate effects from the list, and a computation can only
//! be run when all effects have been handled.
//!
//! # Scholastic Naming
//!
//! | English | Latin | Etymology |
//! |---------|-------|-----------|
//! | Effect | Effectus | *effectus* = result |
//! | Pure | Purus | *purus* = clean, pure |
//! | Member | Membrum | *membrum* = member |
//! | Send | Mittere | *mittere* = to send |
//! | Run | Currere | *currere* = to run |
//!
//! # Status: pure-only facade
//!
//! Only the **pure** path of this monad is implemented. [`send`] constructs a
//! suspended computation for type-level effect tracking, but no interpreter
//! exists to run it: the operation-downcast handler machinery was never
//! implemented. `map` /
//! `flat_map` transform `Purus` values and merely propagate suspensions
//! without composing the supplied function into the continuation. Use this
//! facade for compile-time effect-row checking; use `effects::builtin` /
//! `handler_multi` for runnable handlers.
//!
//! # Example
//!
//! ```rust
//! use ordofp_core::effects::eff::{Eff, ENil, run_purus};
//!
//! // Pure computation (no effects)
//! let pure_comp: Eff<ENil, i32> = Eff::purus(42);
//! let result = run_purus(pure_comp);
//! assert_eq!(result, 42);
//! ```

extern crate alloc;

use alloc::boxed::Box;
use core::marker::PhantomData;

use super::algebraic::EffectusAlgebraicus;
use super::row_v2::{EffectId, EffectRow, EffectSet, assert_has_effect_type};

// =============================================================================
// Eff Monad
// =============================================================================

/// The Eff monad - effectful computation with type-level effect tracking.
///
/// `Eff<R, A>` represents a computation that:
/// - May perform effects from the effect row `R`
/// - Produces a value of type `A` when run
///
/// # Type Parameters
///
/// * `R` - The effect row (type-level list of effects)
/// * `A` - The result type
///
/// # Example
///
/// (pseudo-code — not compilable by design: this predates the v1-to-v2
/// effect-row migration, where rows were type-level lists. The current
/// encoding writes effect membership as a single `EffectSet<MASK>` bitmask
/// const, and the `/* ... */` placeholder for `effectful` was never a real
/// expression to begin with.)
///
/// ```ignore
/// use ordofp::effects::eff::{Eff, ENil};
///
/// // Pure computation
/// let pure: Eff<ENil, i32> = Eff::purus(42);
///
/// // Computation with effects
/// let effectful: Eff<EffectSet<{ 1 << builtin_ids::IO }>, String> = /* ... */;
/// ```
pub struct Eff<R: EffectRow, A> {
    /// The computation wrapped in a box for dynamic dispatch.
    run: Box<dyn FnOnce() -> EffResult<R, A> + Send>,
}

/// The result of running an Eff computation step.
pub enum EffResult<R: EffectRow, A> {
    /// Pure value - computation complete.
    Purus(A),
    /// Suspended on an effect - needs handling.
    Suspensus(EffSuspension<R, A>),
}

/// A suspended effect computation.
pub struct EffSuspension<R: EffectRow, A> {
    /// Type-erased effect operation.
    operation: Box<dyn core::any::Any + Send>,
    /// Type-erased continuation.
    continuation: Box<dyn core::any::Any + Send>,
    _phantom: PhantomData<(R, A)>,
}

impl<R: EffectRow, A: 'static + Send> Eff<R, A> {
    /// Create a pure computation that immediately returns a value.
    ///
    /// > *"Computatio pura"* — Pure computation.
    ///
    /// # Example
    ///
    /// ```rust
    /// use ordofp_core::effects::eff::{Eff, ENil};
    ///
    /// let pure = Eff::<ENil, i32>::purus(42);
    /// ```
    #[inline]
    pub fn purus(value: A) -> Self {
        Eff {
            run: Box::new(move || EffResult::Purus(value)),
        }
    }

    /// Map a function over the result.
    ///
    /// Pure-only: a suspended computation (from [`send`]) is propagated
    /// unchanged and `f` is **not** composed into its continuation — see the
    /// module-level status note.
    ///
    /// # Example
    ///
    /// ```rust
    /// use ordofp_core::effects::eff::{Eff, ENil, run_purus};
    ///
    /// let eff: Eff<ENil, i32> = Eff::purus(21);
    /// let doubled = eff.map(|x| x * 2);
    /// assert_eq!(run_purus(doubled), 42);
    /// ```
    #[inline]
    pub fn map<B, F>(self, f: F) -> Eff<R, B>
    where
        B: 'static + Send,
        F: FnOnce(A) -> B + Send + 'static,
    {
        Eff {
            run: Box::new(move || {
                match (self.run)() {
                    EffResult::Purus(a) => EffResult::Purus(f(a)),
                    EffResult::Suspensus(susp) => {
                        // We need to wrap the continuation to apply f
                        EffResult::Suspensus(EffSuspension {
                            operation: susp.operation,
                            continuation: susp.continuation,
                            _phantom: PhantomData,
                        })
                    }
                }
            }),
        }
    }

    /// Monadic bind (`flat_map`).
    ///
    /// Pure-only: a suspended computation (from [`send`]) is propagated
    /// unchanged and `f` is **not** composed into its continuation — see the
    /// module-level status note.
    ///
    /// # Example
    ///
    /// ```rust
    /// use ordofp_core::effects::eff::{Eff, ENil, run_purus};
    ///
    /// let eff: Eff<ENil, i32> = Eff::purus(41);
    /// let result = eff.flat_map(|x| Eff::purus(x + 1));
    /// assert_eq!(run_purus(result), 42);
    /// ```
    #[inline]
    pub fn flat_map<B, F>(self, f: F) -> Eff<R, B>
    where
        B: 'static + Send,
        F: FnOnce(A) -> Eff<R, B> + Send + 'static,
    {
        Eff {
            run: Box::new(move || match (self.run)() {
                EffResult::Purus(a) => (f(a).run)(),
                EffResult::Suspensus(susp) => EffResult::Suspensus(EffSuspension {
                    operation: susp.operation,
                    continuation: susp.continuation,
                    _phantom: PhantomData,
                }),
            }),
        }
    }

    /// Applicative map2.
    #[inline]
    pub fn map2<B, C, F>(self, other: Eff<R, B>, f: F) -> Eff<R, C>
    where
        B: 'static + Send,
        C: 'static + Send,
        F: FnOnce(A, B) -> C + Send + 'static,
    {
        self.flat_map(move |a| other.map(move |b| f(a, b)))
    }
}

/// Run a pure computation (no effects).
///
/// This can only be called on `Eff<EffectSet<0>, A>` - computations with no effects.
///
/// # Example
///
/// ```rust
/// use ordofp_core::effects::eff::{Eff, run_purus};
///
/// let result = run_purus(Eff::purus(42));
/// assert_eq!(result, 42);
/// ```
///
/// # Panics
///
/// Panics only if a computation typed with the empty effect set
/// `EffectSet<0>` nevertheless suspends on an effect, which the effect-row
/// types make impossible — such a panic indicates a bug in this crate.
#[inline]
pub fn run_purus<A: 'static + Send>(eff: Eff<EffectSet<0>, A>) -> A {
    match (eff.run)() {
        EffResult::Purus(a) => a,
        EffResult::Suspensus(_) => {
            // This should be unreachable for an empty effect set
            panic!("Pure computation suspended on effect - this is a bug")
        }
    }
}

// =============================================================================
// Effect Membership
// =============================================================================

/// Marker trait for effect membership in an effect row.
///
/// `E: Membrum<R>` names the intent that effect `E` is a member of effect
/// row `R`. Actual membership is enforced where operations are sent
/// (`assert_has_effect_type` in [`send`]), since the conditional impl that
/// used to check the row bit here required `generic_const_exprs`.
pub trait Membrum<R: EffectRow>: EffectusAlgebraicus {}

impl<E, R> Membrum<R> for E
where
    E: EffectusAlgebraicus + EffectId,
    R: EffectRow,
{
}

// =============================================================================
// Effect Operations
// =============================================================================

/// Send an effect operation to be handled.
///
/// This creates a computation that performs the given effect operation.
///
/// # Type Parameters
///
/// * `E` - The effect type
/// * `R` - The effect row (must contain `E`)
///
/// # Example
///
/// ```rust
/// use ordofp_core::effects::algebraic::EffectusAlgebraicus;
/// use ordofp_core::effects::eff::{Eff, Membrum, send};
/// use ordofp_core::effects::row_v2::{EffectId, EffectRow, EffectSet};
///
/// struct GetCounter;
///
/// impl EffectusAlgebraicus for GetCounter {
///     type Result = i32;
/// }
///
/// impl EffectId for GetCounter {
///     const ID: u64 = 100;
///     const NAME: &'static str = "GetCounter";
/// }
///
/// fn get_state<R>() -> Eff<R, i32>
/// where
///     GetCounter: Membrum<R>,
///     R: EffectRow,
/// {
///     send(GetCounter)
/// }
///
/// let _eff: Eff<EffectSet<{ 1 << 100 }>, i32> = get_state::<EffectSet<{ 1 << 100 }>>();
/// ```
#[inline]
pub fn send<E, R>(op: E) -> Eff<R, E::Result>
where
    E: EffectusAlgebraicus + EffectId + Send + 'static,
    E::Result: Send + 'static,
    R: EffectRow,
{
    // Compile-time row check, replacing the old `HasEffectType<E>` bound.
    assert_has_effect_type::<R, E>();
    Eff {
        run: Box::new(move || {
            EffResult::Suspensus(EffSuspension {
                operation: Box::new(op),
                continuation: Box::new(()),
                _phantom: PhantomData,
            })
        }),
    }
}

// Deliberately no handler machinery (`interpret`, handler traits): running a
// suspended computation needs an operation-downcast step that does not exist.
// See the module docs for the pure-only status of this facade.

// =============================================================================
// Effect List Type Constructors
// =============================================================================

/// Empty effect list.
///
/// Represents a computation with no effects (pure). Retained as a type alias
/// for readability after the v1 row removal.
pub type ENil = EffectSet<0>;

// Deliberately no `ECons` cons-cell alias: the bitset row encoding has no
// type-level head effect, so effect membership is written directly as
// `EffectSet<MASK>` with an explicit const mask
// (e.g. `EffectSet<{ 1 << builtin_ids::IO }>`).

// =============================================================================
// Utility Functions
// =============================================================================

/// Lift a pure value into the Eff monad.
#[inline]
pub fn pure_eff<R: EffectRow, A: 'static + Send>(a: A) -> Eff<R, A> {
    Eff::purus(a)
}

/// Sequence two Eff computations, discarding the first result.
#[inline]
pub fn then<R: EffectRow, A: 'static + Send, B: 'static + Send>(
    first: Eff<R, A>,
    second: Eff<R, B>,
) -> Eff<R, B> {
    first.flat_map(move |_| second)
}

/// Sequence a vector of Eff computations.
pub fn sequence_eff<R: EffectRow, A: 'static + Send + Clone>(
    effs: alloc::vec::Vec<Eff<R, A>>,
) -> Eff<R, alloc::vec::Vec<A>> {
    effs.into_iter()
        .fold(Eff::purus(alloc::vec::Vec::new()), |acc, eff| {
            acc.flat_map(move |mut vec| {
                eff.map(move |a| {
                    vec.push(a);
                    vec
                })
            })
        })
}

/// Traverse a collection with an effectful function.
pub fn traverse_eff<R, A, B, F>(items: alloc::vec::Vec<A>, f: F) -> Eff<R, alloc::vec::Vec<B>>
where
    R: EffectRow,
    A: 'static + Send,
    B: 'static + Send + Clone,
    F: Fn(A) -> Eff<R, B> + Clone + Send + 'static,
{
    items
        .into_iter()
        .fold(Eff::purus(alloc::vec::Vec::new()), move |acc, a| {
            let f = f.clone();
            acc.flat_map(move |mut vec| {
                f(a).map(move |b| {
                    vec.push(b);
                    vec
                })
            })
        })
}

// =============================================================================
// Tests
// =============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::vec;

    #[test]
    fn test_eff_purus() {
        let eff: Eff<ENil, i32> = Eff::purus(42);
        let result = run_purus(eff);
        assert_eq!(result, 42);
    }

    #[test]
    fn test_eff_map() {
        let eff: Eff<ENil, i32> = Eff::purus(21);
        let doubled = eff.map(|x| x * 2);
        let result = run_purus(doubled);
        assert_eq!(result, 42);
    }

    #[test]
    fn test_eff_flat_map() {
        let eff: Eff<ENil, i32> = Eff::purus(20);
        let result = eff.flat_map(|x| Eff::purus(x + 22));
        assert_eq!(run_purus(result), 42);
    }

    #[test]
    fn test_eff_map2() {
        let eff1: Eff<ENil, i32> = Eff::purus(20);
        let eff2: Eff<ENil, i32> = Eff::purus(22);
        let sum = eff1.map2(eff2, |a, b| a + b);
        assert_eq!(run_purus(sum), 42);
    }

    #[test]
    fn test_pure_eff() {
        let eff: Eff<ENil, &str> = pure_eff("hello");
        assert_eq!(run_purus(eff), "hello");
    }

    #[test]
    fn test_then() {
        let first: Eff<ENil, i32> = Eff::purus(1);
        let second: Eff<ENil, &str> = Eff::purus("result");
        let result = then(first, second);
        assert_eq!(run_purus(result), "result");
    }

    #[test]
    fn test_sequence_eff() {
        let effs: alloc::vec::Vec<Eff<ENil, i32>> =
            vec![Eff::purus(1), Eff::purus(2), Eff::purus(3)];
        let sequenced = sequence_eff(effs);
        assert_eq!(run_purus(sequenced), vec![1, 2, 3]);
    }

    #[test]
    fn test_traverse_eff() {
        let items = vec![1, 2, 3];
        let result = traverse_eff(items, |x| Eff::<ENil, _>::purus(x * 2));
        assert_eq!(run_purus(result), vec![2, 4, 6]);
    }
}