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
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
//! Evidence-Based Effect Handlers - Koka-style evidence passing
//!
//! > *"Testimonium est probatio"*
//! > — Evidence is proof. (Legal maxim)
//!
//! This module implements evidence-based effect handlers inspired by Koka's
//! generalized evidence passing. Evidence allows effect handlers to be passed
//! implicitly through computations, enabling efficient effect handling.
//!
//! # Design
//!
//! Evidence-based effects decompose into:
//! - **Effect Tags**: Unique identifiers for effect types
//! - **Evidence**: Proof that an effect handler is available
//! - **Evidence Vectors**: Collections of evidence for multiple effects
//! - **Handler Clauses**: Different ways to handle operations
//!
//! # Inspired By
//!
//! - Koka's evidence-based algebraic effects
//! - "Generalized Evidence Passing for Effect Handlers" paper
//! - "Effect Handlers, Evidently" paper
//!
//! # Scholastic Naming
//!
//! | English | Latin | Etymology |
//! |---------|-------|-----------|
//! | Evidence | Testimonium | *testimonium* = testimony |
//! | Tag | Signum | *signum* = sign, mark |
//! | Vector | Vector | *vector* = carrier |
//! | Clause | Clausula | *clausula* = small clause |
//! | Resume | Resumere | *resumere* = to take back |

extern crate alloc;

use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::TypeId;
use core::marker::PhantomData;

use super::algebraic::EffectusAlgebraicus;

// =============================================================================
// Effect Tags
// =============================================================================

/// An effect tag uniquely identifies an effect type.
///
/// `SignumEffectus<E>` serves as a runtime type tag for effect `E`,
/// allowing dynamic effect dispatch.
///
/// > *"Signum effectus"* — Sign of the effect.
#[derive(Debug)]
pub struct SignumEffectus<E: EffectusAlgebraicus> {
    /// Human-readable name for debugging.
    nomen: &'static str,
    /// Type ID for runtime identification.
    type_id: TypeId,
    _phantom: PhantomData<E>,
}

impl<E: EffectusAlgebraicus + 'static> SignumEffectus<E> {
    /// Create a new effect tag with the given name.
    #[inline]
    pub const fn new(nomen: &'static str) -> Self {
        SignumEffectus {
            nomen,
            type_id: TypeId::of::<E>(),
            _phantom: PhantomData,
        }
    }

    /// Get the tag's name.
    #[inline]
    pub fn nomen(&self) -> &'static str {
        self.nomen
    }

    /// Get the type ID.
    #[inline]
    pub fn type_id(&self) -> TypeId {
        self.type_id
    }

    /// Check if this tag matches another effect type.
    #[inline]
    pub fn matches<F: EffectusAlgebraicus + 'static>(&self) -> bool {
        self.type_id == TypeId::of::<F>()
    }
}

impl<E: EffectusAlgebraicus + 'static> Clone for SignumEffectus<E> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<E: EffectusAlgebraicus + 'static> Copy for SignumEffectus<E> {}

// =============================================================================
// Evidence
// =============================================================================

/// Evidence that an effect handler is available.
///
/// `Testimonium<E, R>` proves that effect `E` can be handled in context `R`.
/// Evidence carries the handler and enables implicit handler passing.
///
/// > *"Testimonium tractatoris"* — Evidence of the handler.
///
/// # Example
///
/// ```rust
/// use ordofp_core::effects::algebraic::EffectusAlgebraicus;
/// use ordofp_core::effects::testimonium::{SignumEffectus, Testimonium};
///
/// #[derive(Debug, Clone)]
/// struct MyEffect;
///
/// impl EffectusAlgebraicus for MyEffect {
///     type Result = i32;
/// }
///
/// let tag = SignumEffectus::<MyEffect>::new("MyEffect");
/// let evidence = Testimonium::new(tag, 42i32);
/// assert_eq!(*evidence.tractator(), 42);
/// ```
pub struct Testimonium<E: EffectusAlgebraicus, H> {
    /// The effect tag.
    signum: SignumEffectus<E>,
    /// The effect handler.
    tractator: H,
    /// Handler depth in the handler stack.
    depth: usize,
}

impl<E: EffectusAlgebraicus + 'static, H> Testimonium<E, H> {
    /// Create new evidence with the given handler.
    #[inline]
    pub fn new(signum: SignumEffectus<E>, tractator: H) -> Self {
        Testimonium {
            signum,
            tractator,
            depth: 0,
        }
    }

    /// Create evidence at a specific depth.
    #[inline]
    pub fn with_depth(signum: SignumEffectus<E>, tractator: H, depth: usize) -> Self {
        Testimonium {
            signum,
            tractator,
            depth,
        }
    }

    /// Get the effect tag.
    #[inline]
    pub fn signum(&self) -> &SignumEffectus<E> {
        &self.signum
    }

    /// Get a reference to the handler.
    #[inline]
    pub fn tractator(&self) -> &H {
        &self.tractator
    }

    /// Get a mutable reference to the handler.
    #[inline]
    pub fn tractator_mut(&mut self) -> &mut H {
        &mut self.tractator
    }

    /// Get the handler depth.
    #[inline]
    pub fn depth(&self) -> usize {
        self.depth
    }

    /// Consume and return the handler.
    #[inline]
    pub fn into_tractator(self) -> H {
        self.tractator
    }
}

// =============================================================================
// Evidence Vector
// =============================================================================

/// A vector of evidence for multiple effects.
///
/// `VectorTestimonium` stores evidence for all effects in scope,
/// allowing dynamic effect lookup and dispatch.
///
/// > *"Vector testimoniorum"* — Vector of evidence.
///
/// # Example
///
/// ```rust
/// use ordofp_core::effects::algebraic::EffectusAlgebraicus;
/// use ordofp_core::effects::testimonium::{SignumEffectus, Testimonium, VectorTestimonium};
///
/// #[derive(Debug, Clone)]
/// struct MyEffect;
///
/// impl EffectusAlgebraicus for MyEffect {
///     type Result = i32;
/// }
///
/// let mut evv = VectorTestimonium::new();
/// let tag = SignumEffectus::<MyEffect>::new("MyEffect");
/// let evidence = Testimonium::new(tag, ());
/// evv.push(evidence);
/// assert_eq!(evv.len(), 1);
/// assert!(evv.has::<MyEffect>());
/// ```
pub struct VectorTestimonium {
    /// Type-erased evidence entries.
    entries: Vec<TestimoniumEntry>,
}

/// A type-erased evidence entry.
struct TestimoniumEntry {
    /// Type ID of the effect.
    type_id: TypeId,
    /// Type-erased evidence. Underscore-named because it is stored but not
    /// yet retrieved — retrieval needs additional type machinery for safe
    /// downcasting in effect handlers.
    _evidence: Box<dyn core::any::Any + Send + Sync>,
}

impl VectorTestimonium {
    /// Create an empty evidence vector.
    #[inline]
    pub fn new() -> Self {
        VectorTestimonium {
            entries: Vec::new(),
        }
    }

    /// Create an evidence vector with capacity.
    #[inline]
    pub fn with_capacity(cap: usize) -> Self {
        VectorTestimonium {
            entries: Vec::with_capacity(cap),
        }
    }

    /// Push evidence onto the vector.
    #[inline]
    pub fn push<E, H>(&mut self, evidence: Testimonium<E, H>)
    where
        E: EffectusAlgebraicus + 'static,
        H: Send + Sync + 'static,
    {
        self.entries.push(TestimoniumEntry {
            type_id: TypeId::of::<E>(),
            _evidence: Box::new(evidence),
        });
    }

    /// Look up evidence for an effect type.
    #[inline]
    pub fn lookup<E: EffectusAlgebraicus + 'static>(&self) -> Option<usize> {
        let target = TypeId::of::<E>();
        self.entries.iter().position(|e| e.type_id == target)
    }

    /// Check if evidence exists for an effect.
    #[inline]
    pub fn has<E: EffectusAlgebraicus + 'static>(&self) -> bool {
        self.lookup::<E>().is_some()
    }

    /// Get the number of evidence entries.
    #[inline]
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Check if the vector is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// Clear all evidence.
    #[inline]
    pub fn clear(&mut self) {
        self.entries.clear();
    }

    /// Pop the last evidence entry.
    #[inline]
    pub fn pop(&mut self) {
        self.entries.pop();
    }
}

impl Default for VectorTestimonium {
    fn default() -> Self {
        Self::new()
    }
}

// =============================================================================
// Handler Clauses (Clausula)
// =============================================================================

/// Handler clause types for effect operations.
///
/// `Clausula` describes how an effect operation should be handled:
/// - `Fun`: Tail-resumptive (operation resumes immediately)
/// - `Ctl`: Control (operation may manipulate continuation)
/// - `Final`: Final (operation never resumes)
///
/// > *"Clausula tractatoris"* — Handler clause.
///
/// # Koka Correspondence
///
/// These correspond to Koka's handler clause types:
/// - `fun` -> `Fun` (tail-resumptive)
/// - `control` -> `Ctl` (control operation)
/// - `final` -> `Final` (never resumes)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClausulaGenus {
    /// Tail-resumptive: operation returns directly.
    Fun,
    /// Control: operation has access to continuation.
    Ctl,
    /// Final: operation never resumes.
    Final,
}

/// A handler clause that handles an operation.
///
/// `Clausula<A, B, E, R>` handles operation input `A` to produce `B`,
/// possibly using effect `E` in context `R`.
pub enum Clausula<A, B, E: EffectusAlgebraicus, R> {
    /// Tail-resumptive clause: simple function.
    Fun(Box<dyn Fn(A) -> B + Send + Sync>),

    /// Control clause: has access to resumption.
    Ctl(Box<dyn Fn(A, Resumptio<B, R>) -> R + Send + Sync>),

    /// Final clause: never resumes.
    Final(Box<dyn Fn(A) -> R + Send + Sync>),

    /// Carries `Infallible` so this variant can never actually be
    /// constructed; it exists solely so `E` is used in the enum body.
    #[doc(hidden)]
    _Phantom(PhantomData<E>, core::convert::Infallible),
}

impl<A, B, E: EffectusAlgebraicus, R> Clausula<A, B, E, R> {
    /// Create a tail-resumptive clause.
    #[inline]
    pub fn fun<F>(f: F) -> Self
    where
        F: Fn(A) -> B + Send + Sync + 'static,
    {
        Clausula::Fun(Box::new(f))
    }

    /// Create a control clause.
    #[inline]
    pub fn ctl<F>(f: F) -> Self
    where
        F: Fn(A, Resumptio<B, R>) -> R + Send + Sync + 'static,
    {
        Clausula::Ctl(Box::new(f))
    }

    /// Create a final clause.
    #[inline]
    pub fn final_<F>(f: F) -> Self
    where
        F: Fn(A) -> R + Send + Sync + 'static,
    {
        Clausula::Final(Box::new(f))
    }

    /// Get the clause type.
    #[inline]
    pub fn genus(&self) -> ClausulaGenus {
        match self {
            Clausula::Fun(_) => ClausulaGenus::Fun,
            Clausula::Ctl(_) => ClausulaGenus::Ctl,
            Clausula::Final(_) => ClausulaGenus::Final,
            Clausula::_Phantom(_, never) => match *never {},
        }
    }
}

// =============================================================================
// Resumption
// =============================================================================

/// A resumption (continuation) for control operations.
///
/// `Resumptio<A, R>` represents the continuation from an effect operation.
/// It can be called to resume the computation with a value.
///
/// > *"Resumptio computationis"* — Resumption of computation.
///
/// # One-Shot Semantics
///
/// Resumptions are one-shot: they can only be called once.
/// This matches OCaml 5.0 and Koka's continuation semantics.
pub struct Resumptio<A, R> {
    /// The continuation function.
    continuation: Box<dyn FnOnce(A) -> R + Send>,
}

impl<A: 'static, R: 'static> Resumptio<A, R> {
    /// Create a new resumption.
    #[inline]
    pub fn new<F>(f: F) -> Self
    where
        F: FnOnce(A) -> R + Send + 'static,
    {
        Resumptio {
            continuation: Box::new(f),
        }
    }

    /// Resume the computation with a value.
    ///
    /// One-shot semantics are enforced statically: `resume` consumes the
    /// resumption by move, so it cannot be called twice.
    #[inline]
    pub fn resume(self, value: A) -> R {
        (self.continuation)(value)
    }

    /// Transform the resumption's output.
    #[inline]
    pub fn map<S: 'static, F>(self, f: F) -> Resumptio<A, S>
    where
        F: FnOnce(R) -> S + Send + 'static,
    {
        Resumptio::new(move |a| f(self.resume(a)))
    }

    /// Transform the resumption's input.
    #[inline]
    pub fn contramap<B: 'static, F>(self, f: F) -> Resumptio<B, R>
    where
        F: FnOnce(B) -> A + Send + 'static,
    {
        Resumptio::new(move |b| self.resume(f(b)))
    }
}

// =============================================================================
// Evidence-Based Handler
// =============================================================================

/// An evidence-based effect handler.
///
/// `TractatorEvidentia<E>` uses evidence passing for efficient effect handling.
pub trait TractatorEvidentia<E: EffectusAlgebraicus>: Sized {
    /// The handler's result type.
    type Output;

    /// Get the effect tag for this handler.
    fn signum(&self) -> SignumEffectus<E>;

    /// Handle a return value.
    fn handle_return(&self, value: Self::Output) -> Self::Output;

    /// Handle an effect operation with evidence.
    fn handle_operation(
        &mut self,
        evv: &VectorTestimonium,
        op: E,
        k: Resumptio<E::Result, Self::Output>,
    ) -> Self::Output;
}

/// Run a computation with evidence-based handling.
///
/// Note: the handler is currently recorded as a unit placeholder in the
/// evidence vector; full handler threading is not yet implemented.
// Takes the handler by value now so that wiring it into the evidence vector
// later is not a breaking signature change.
#[allow(clippy::needless_pass_by_value)]
#[inline]
pub fn run_with_evidence<E, H, A, F>(handler: H, evv: &mut VectorTestimonium, computation: F) -> A
where
    E: EffectusAlgebraicus + 'static,
    H: TractatorEvidentia<E, Output = A> + Send + Sync + 'static,
    F: FnOnce(&VectorTestimonium) -> A,
    A: 'static,
{
    // Push evidence for this handler
    let signum = handler.signum();
    evv.push(Testimonium::new(signum, ()));

    let result = computation(evv);
    let result = handler.handle_return(result);

    // Pop evidence
    evv.pop();

    result
}

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

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

    // Test effect
    #[derive(Debug, Clone)]
    struct TestEffect;

    impl EffectusAlgebraicus for TestEffect {
        type Result = i32;
    }

    #[test]
    fn test_signum_effectus() {
        let tag: SignumEffectus<TestEffect> = SignumEffectus::new("TestEffect");
        assert_eq!(tag.nomen(), "TestEffect");
        assert!(tag.matches::<TestEffect>());
    }

    #[test]
    fn test_testimonium() {
        let tag = SignumEffectus::<TestEffect>::new("TestEffect");
        let evidence = Testimonium::new(tag, 42i32);

        assert_eq!(evidence.depth(), 0);
        assert_eq!(*evidence.tractator(), 42);
    }

    #[test]
    fn test_vector_testimonium() {
        let mut evv = VectorTestimonium::new();
        assert!(evv.is_empty());

        let tag = SignumEffectus::<TestEffect>::new("TestEffect");
        let evidence = Testimonium::new(tag, ());
        evv.push(evidence);

        assert_eq!(evv.len(), 1);
        assert!(evv.has::<TestEffect>());
    }

    #[test]
    fn test_clausula_genus() {
        let fun_clause: Clausula<i32, i32, TestEffect, i32> = Clausula::fun(|x| x * 2);
        assert_eq!(fun_clause.genus(), ClausulaGenus::Fun);

        let final_clause: Clausula<i32, i32, TestEffect, i32> = Clausula::final_(|x| x);
        assert_eq!(final_clause.genus(), ClausulaGenus::Final);
    }

    #[test]
    fn test_resumptio() {
        let k = Resumptio::new(|x: i32| x * 2);
        let result = k.resume(21);
        assert_eq!(result, 42);
    }

    #[test]
    fn test_resumptio_map() {
        let k = Resumptio::new(|x: i32| x * 2);
        let mapped = k.map(|r| r + 1);
        assert_eq!(mapped.resume(20), 41);
    }

    #[test]
    fn test_resumptio_contramap() {
        let k = Resumptio::new(|x: i32| x * 2);
        let contramapped = k.contramap(|s: &str| s.len() as i32);
        assert_eq!(contramapped.resume("hello"), 10);
    }

    #[test]
    fn test_resumptio_one_shot() {
        // One-shot semantics are enforced by Rust's move semantics.
        // After calling resume, `k` is consumed and cannot be used again.
        let k = Resumptio::new(|x: i32| x);
        let result = k.resume(42);
        assert_eq!(result, 42);
        // Attempting to call k.resume(1) again would be a compile error:
        // "error: use of moved value: `k`"
    }
}