Skip to main content

oxilean_std/thunk/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::cell::OnceCell;
6
7use super::functions::*;
8use std::collections::HashMap;
9
10/// Applicative functor over thunks.
11#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct ThunkApp<A: Clone> {
14    pub val: A,
15}
16#[allow(dead_code)]
17impl<A: Clone> ThunkApp<A> {
18    pub fn pure(a: A) -> Self {
19        ThunkApp { val: a }
20    }
21    pub fn map<B: Clone, F: Fn(A) -> B>(self, f: F) -> ThunkApp<B> {
22        ThunkApp { val: f(self.val) }
23    }
24    pub fn ap<B: Clone, F>(self, tf: ThunkApp<F>) -> ThunkApp<B>
25    where
26        F: Fn(A) -> B + Clone,
27    {
28        ThunkApp {
29            val: (tf.val)(self.val),
30        }
31    }
32}
33/// A memoized function.
34///
35/// Wraps a function `f : A → B` such that each call is cached by key.
36pub struct Memo<A: std::hash::Hash + Eq + Clone, B: Clone> {
37    f: Box<dyn Fn(&A) -> B>,
38    cache: std::collections::HashMap<A, B>,
39}
40impl<A: std::hash::Hash + Eq + Clone, B: Clone> Memo<A, B> {
41    /// Create a new memoized function.
42    pub fn new<F: Fn(&A) -> B + 'static>(f: F) -> Self {
43        Self {
44            f: Box::new(f),
45            cache: std::collections::HashMap::new(),
46        }
47    }
48    /// Call the memoized function, caching the result.
49    pub fn call(&mut self, arg: &A) -> &B {
50        if !self.cache.contains_key(arg) {
51            let result = (self.f)(arg);
52            self.cache.insert(arg.clone(), result);
53        }
54        &self.cache[arg]
55    }
56    /// Clear the cache.
57    pub fn clear_cache(&mut self) {
58        self.cache.clear();
59    }
60    /// Number of cached entries.
61    pub fn cache_size(&self) -> usize {
62        self.cache.len()
63    }
64}
65/// A lazy tree structure where children are computed on demand.
66pub enum ThunkTree<T> {
67    /// A leaf node.
68    Leaf(T),
69    /// A lazy node whose children are thunked.
70    Node(Box<dyn Fn() -> Vec<ThunkTree<T>>>),
71}
72impl<T: Clone> ThunkTree<T> {
73    /// Create a leaf.
74    pub fn leaf(value: T) -> Self {
75        Self::Leaf(value)
76    }
77    /// Create a lazy node.
78    pub fn node<F: Fn() -> Vec<ThunkTree<T>> + 'static>(f: F) -> Self {
79        Self::Node(Box::new(f))
80    }
81    /// Collect all leaves by forcing the tree recursively.
82    pub fn leaves(&self) -> Vec<T>
83    where
84        T: Clone,
85    {
86        match self {
87            ThunkTree::Leaf(v) => vec![v.clone()],
88            ThunkTree::Node(f) => f().iter().flat_map(|c| c.leaves()).collect(),
89        }
90    }
91    /// Check if this is a leaf.
92    pub fn is_leaf(&self) -> bool {
93        matches!(self, ThunkTree::Leaf(_))
94    }
95}
96/// A lazily-built list (stream-like).
97///
98/// Each element is produced by a function, allowing potentially infinite streams
99/// to be represented finitely and evaluated on demand.
100pub struct LazyList<T> {
101    produced: Vec<T>,
102    next: Option<Box<dyn Fn(usize) -> Option<T>>>,
103}
104impl<T: Clone> LazyList<T> {
105    /// Create a list backed by a generating function.
106    pub fn from_fn<F: Fn(usize) -> Option<T> + 'static>(f: F) -> Self {
107        Self {
108            produced: Vec::new(),
109            next: Some(Box::new(f)),
110        }
111    }
112    /// Get the element at index `i`.
113    pub fn get(&mut self, i: usize) -> Option<&T> {
114        while self.produced.len() <= i {
115            let idx = self.produced.len();
116            if let Some(f) = &self.next {
117                match f(idx) {
118                    Some(v) => self.produced.push(v),
119                    None => {
120                        self.next = None;
121                        break;
122                    }
123                }
124            } else {
125                break;
126            }
127        }
128        self.produced.get(i)
129    }
130    /// Take the first `n` elements, forcing them.
131    pub fn take(&mut self, n: usize) -> Vec<T> {
132        (0..n).filter_map(|i| self.get(i).cloned()).collect()
133    }
134    /// Number of elements produced so far.
135    pub fn produced_count(&self) -> usize {
136        self.produced.len()
137    }
138}
139/// A deferred value that may fail to compute.
140pub struct TryThunk<T, E> {
141    init: Option<Box<dyn FnOnce() -> Result<T, E>>>,
142    pub(super) value: Option<Result<T, E>>,
143}
144impl<T, E> TryThunk<T, E> {
145    /// Create a new try-thunk.
146    pub fn new<F: FnOnce() -> Result<T, E> + 'static>(f: F) -> Self {
147        Self {
148            init: Some(Box::new(f)),
149            value: None,
150        }
151    }
152    /// Force the thunk. Returns `&Result<T, E>`.
153    pub fn force(&mut self) -> &Result<T, E> {
154        if self.value.is_none() {
155            if let Some(f) = self.init.take() {
156                self.value = Some(f());
157            }
158        }
159        self.value
160            .as_ref()
161            .expect("TryThunk: value missing after force")
162    }
163    /// Check if already forced.
164    pub fn is_forced(&self) -> bool {
165        self.value.is_some()
166    }
167}
168/// A lazy stream (coinductive list) implemented as a linked structure of thunks.
169///
170/// Models the cofree comonad / productive corecursion pattern.
171#[allow(dead_code)]
172pub enum LazyStream<T> {
173    /// Empty stream.
174    Nil,
175    /// A head value paired with a lazily-computed tail.
176    Cons(T, Box<dyn FnOnce() -> LazyStream<T>>),
177}
178#[allow(dead_code)]
179impl<T: Clone> LazyStream<T> {
180    /// Construct an empty stream.
181    pub fn nil() -> Self {
182        LazyStream::Nil
183    }
184    /// Construct a cons cell.
185    pub fn cons<F: FnOnce() -> LazyStream<T> + 'static>(head: T, tail: F) -> Self {
186        LazyStream::Cons(head, Box::new(tail))
187    }
188    /// Force the stream and collect up to `n` elements.
189    pub fn take(self, n: usize) -> Vec<T> {
190        let mut result = Vec::new();
191        let mut current = self;
192        for _ in 0..n {
193            match current {
194                LazyStream::Nil => break,
195                LazyStream::Cons(h, tl) => {
196                    result.push(h);
197                    current = tl();
198                }
199            }
200        }
201        result
202    }
203    /// Check if the stream is empty (without forcing the tail).
204    pub fn is_nil(&self) -> bool {
205        matches!(self, LazyStream::Nil)
206    }
207}
208/// A sequence of thunks evaluated lazily.
209///
210/// Each element is computed on demand and cached independently.
211pub struct ThunkSeq<T> {
212    items: Vec<Box<dyn Fn() -> T>>,
213    cache: Vec<Option<T>>,
214}
215impl<T: Clone> ThunkSeq<T> {
216    /// Create an empty sequence.
217    pub fn new() -> Self {
218        Self {
219            items: Vec::new(),
220            cache: Vec::new(),
221        }
222    }
223    /// Push a lazy element.
224    pub fn push<F: Fn() -> T + 'static>(&mut self, f: F) {
225        self.items.push(Box::new(f));
226        self.cache.push(None);
227    }
228    /// Get the element at index `i`, forcing it if needed.
229    pub fn get(&mut self, i: usize) -> Option<&T> {
230        if i >= self.items.len() {
231            return None;
232        }
233        if self.cache[i].is_none() {
234            self.cache[i] = Some((self.items[i])());
235        }
236        self.cache[i].as_ref()
237    }
238    /// Number of elements.
239    pub fn len(&self) -> usize {
240        self.items.len()
241    }
242    /// Check if empty.
243    pub fn is_empty(&self) -> bool {
244        self.items.is_empty()
245    }
246    /// Count forced elements.
247    pub fn forced_count(&self) -> usize {
248        self.cache.iter().filter(|c| c.is_some()).count()
249    }
250}
251/// A thunk backed by `OnceCell` for safe lazy initialization.
252pub struct OnceCellThunk<T> {
253    pub(super) cell: OnceCell<T>,
254    init: Option<Box<dyn FnOnce() -> T>>,
255}
256impl<T> OnceCellThunk<T> {
257    /// Create a new `OnceCellThunk` with the given initializer.
258    pub fn new<F: FnOnce() -> T + 'static>(f: F) -> Self {
259        Self {
260            cell: OnceCell::new(),
261            init: Some(Box::new(f)),
262        }
263    }
264    /// Force the thunk, returning a reference to the value.
265    pub fn force(&mut self) -> &T {
266        if self.cell.get().is_none() {
267            if let Some(f) = self.init.take() {
268                let _ = self.cell.set(f());
269            }
270        }
271        self.cell.get().expect("OnceCellThunk: init was None")
272    }
273    /// Check whether the thunk has been forced.
274    pub fn is_forced(&self) -> bool {
275        self.cell.get().is_some()
276    }
277}
278/// Cofree comonad: infinite stream with comonadic structure.
279#[allow(dead_code)]
280#[derive(Debug, Clone)]
281pub struct CofreeStream<A> {
282    pub elements: Vec<A>,
283    pub period: usize,
284}
285#[allow(dead_code)]
286impl<A: Clone> CofreeStream<A> {
287    pub fn from_periodic(seed: Vec<A>) -> Self {
288        let len = seed.len().max(1);
289        CofreeStream {
290            elements: seed,
291            period: len,
292        }
293    }
294    pub fn extract(&self) -> &A {
295        &self.elements[0]
296    }
297    pub fn tail(&self) -> Self {
298        if self.elements.len() <= 1 {
299            return self.clone();
300        }
301        let mut new_elems = self.elements[1..].to_vec();
302        new_elems.push(self.elements[0].clone());
303        CofreeStream {
304            elements: new_elems,
305            period: self.period,
306        }
307    }
308    pub fn nth(&self, n: usize) -> &A {
309        &self.elements[n % self.elements.len()]
310    }
311    pub fn map<B, F: Fn(&A) -> B>(&self, f: F) -> CofreeStream<B> {
312        CofreeStream {
313            elements: self.elements.iter().map(f).collect(),
314            period: self.period,
315        }
316    }
317}
318/// Demand-driven computation graph node.
319#[allow(dead_code)]
320#[derive(Debug, Clone)]
321pub struct ComputeNode<T: Clone> {
322    pub name: String,
323    pub value: Option<T>,
324    pub deps: Vec<String>,
325    pub is_dirty: bool,
326}
327#[allow(dead_code)]
328impl<T: Clone> ComputeNode<T> {
329    pub fn new(name: &str) -> Self {
330        ComputeNode {
331            name: name.to_string(),
332            value: None,
333            deps: Vec::new(),
334            is_dirty: true,
335        }
336    }
337    pub fn add_dep(&mut self, dep: &str) {
338        self.deps.push(dep.to_string());
339    }
340    pub fn set_value(&mut self, val: T) {
341        self.value = Some(val);
342        self.is_dirty = false;
343    }
344    pub fn invalidate(&mut self) {
345        self.value = None;
346        self.is_dirty = true;
347    }
348    pub fn get(&self) -> Option<&T> {
349        self.value.as_ref()
350    }
351}
352/// A simple game-semantics arena: tracks questions and answers for a thunk-like computation.
353///
354/// In game semantics, evaluation of `Thunk α` corresponds to a two-player game
355/// where Opponent asks "what is the value?" and Proponent answers with `α`.
356/// This struct tracks that dialogue.
357#[allow(dead_code)]
358pub struct GameArena {
359    /// Moves played so far (question index, answer value).
360    moves: Vec<(usize, String)>,
361    /// Counter for generating fresh question indices.
362    question_counter: usize,
363}
364#[allow(dead_code)]
365impl GameArena {
366    /// Create an empty arena.
367    pub fn new() -> Self {
368        Self {
369            moves: Vec::new(),
370            question_counter: 0,
371        }
372    }
373    /// Opponent asks a question; returns the question index.
374    pub fn ask(&mut self) -> usize {
375        let q = self.question_counter;
376        self.question_counter += 1;
377        q
378    }
379    /// Proponent answers question `q` with value `v`.
380    pub fn answer(&mut self, q: usize, v: impl Into<String>) {
381        self.moves.push((q, v.into()));
382    }
383    /// Find the answer to question `q`, if any.
384    pub fn get_answer(&self, q: usize) -> Option<&str> {
385        self.moves
386            .iter()
387            .find(|(qi, _)| *qi == q)
388            .map(|(_, a)| a.as_str())
389    }
390    /// Number of moves played.
391    pub fn move_count(&self) -> usize {
392        self.moves.len()
393    }
394    /// Check if the game is "complete" (every question has an answer).
395    pub fn is_complete(&self) -> bool {
396        let answered: std::collections::HashSet<usize> =
397            self.moves.iter().map(|(q, _)| *q).collect();
398        (0..self.question_counter).all(|q| answered.contains(&q))
399    }
400}
401/// A future-like value that becomes available after a delay (modelled as count).
402#[derive(Debug, Clone)]
403pub struct Delayed<T> {
404    value: T,
405    delay_steps: usize,
406    elapsed: usize,
407}
408impl<T: Clone> Delayed<T> {
409    /// Create a delayed value that becomes available after `steps` evaluations.
410    pub fn new(value: T, steps: usize) -> Self {
411        Self {
412            value,
413            delay_steps: steps,
414            elapsed: 0,
415        }
416    }
417    /// Advance one step. Returns the value when ready.
418    pub fn step(&mut self) -> Option<T> {
419        self.elapsed += 1;
420        if self.elapsed >= self.delay_steps {
421            Some(self.value.clone())
422        } else {
423            None
424        }
425    }
426    /// Check if ready.
427    pub fn is_ready(&self) -> bool {
428        self.elapsed >= self.delay_steps
429    }
430    /// Remaining steps.
431    pub fn remaining(&self) -> usize {
432        self.delay_steps.saturating_sub(self.elapsed)
433    }
434}
435/// Blackhole detection for cyclic thunk evaluation.
436#[allow(dead_code)]
437#[derive(Debug, Clone, PartialEq)]
438pub enum ThunkState<T> {
439    Unevaluated,
440    Evaluating,
441    Evaluated(T),
442    Error(String),
443}
444#[allow(dead_code)]
445impl<T: Clone> ThunkState<T> {
446    pub fn is_value(&self) -> bool {
447        matches!(self, ThunkState::Evaluated(_))
448    }
449    pub fn is_blackhole(&self) -> bool {
450        matches!(self, ThunkState::Evaluating)
451    }
452    pub fn get_value(&self) -> Option<&T> {
453        match self {
454            ThunkState::Evaluated(v) => Some(v),
455            _ => None,
456        }
457    }
458    pub fn enter(&mut self) -> bool {
459        match self {
460            ThunkState::Unevaluated => {
461                *self = ThunkState::Evaluating;
462                true
463            }
464            ThunkState::Evaluating => false,
465            _ => false,
466        }
467    }
468    pub fn fill(&mut self, val: T) {
469        *self = ThunkState::Evaluated(val);
470    }
471    pub fn fail(&mut self, msg: &str) {
472        *self = ThunkState::Error(msg.to_string());
473    }
474}
475/// A Kleisli arrow in the Thunk monad: `A → Thunk B` represented as a Rust closure.
476///
477/// Provides composition via cokleisli / kleisli combinators.
478#[allow(dead_code)]
479pub struct ThunkKleisli<A, B> {
480    arrow: Box<dyn Fn(A) -> Option<B>>,
481}
482#[allow(dead_code)]
483impl<A: 'static, B: Clone + 'static> ThunkKleisli<A, B> {
484    /// Create a Kleisli arrow from a function.
485    pub fn new<F: Fn(A) -> Option<B> + 'static>(f: F) -> Self {
486        Self { arrow: Box::new(f) }
487    }
488    /// Apply the arrow.
489    pub fn apply(&self, a: A) -> Option<B> {
490        (self.arrow)(a)
491    }
492    /// Compose this arrow with another: `self >=> other`.
493    pub fn compose<C: Clone + 'static>(self, other: ThunkKleisli<B, C>) -> ThunkKleisli<A, C> {
494        ThunkKleisli::new(move |a| {
495            let b = (self.arrow)(a)?;
496            other.apply(b)
497        })
498    }
499}
500/// Suspension monad: delayed computation with abort capability.
501#[allow(dead_code)]
502#[derive(Debug, Clone)]
503pub enum Suspension<A> {
504    Done(A),
505    Suspended(String),
506    Aborted(String),
507}
508#[allow(dead_code)]
509impl<A: Clone> Suspension<A> {
510    pub fn pure(a: A) -> Self {
511        Suspension::Done(a)
512    }
513    pub fn suspend(desc: &str) -> Self {
514        Suspension::Suspended(desc.to_string())
515    }
516    pub fn abort(msg: &str) -> Self {
517        Suspension::Aborted(msg.to_string())
518    }
519    pub fn is_done(&self) -> bool {
520        matches!(self, Suspension::Done(_))
521    }
522    pub fn map<B: Clone, F: Fn(A) -> B>(self, f: F) -> Suspension<B> {
523        match self {
524            Suspension::Done(a) => Suspension::Done(f(a)),
525            Suspension::Suspended(d) => Suspension::Suspended(d),
526            Suspension::Aborted(e) => Suspension::Aborted(e),
527        }
528    }
529    pub fn and_then<B: Clone, F: Fn(A) -> Suspension<B>>(self, f: F) -> Suspension<B> {
530        match self {
531            Suspension::Done(a) => f(a),
532            Suspension::Suspended(d) => Suspension::Suspended(d),
533            Suspension::Aborted(e) => Suspension::Aborted(e),
534        }
535    }
536}
537/// A comonad-style context: wraps a focused value with an environment.
538///
539/// Models `(env, focus)` pairs where `focus` is the current value and
540/// `env` captures additional context (e.g., for zipper traversal).
541#[allow(dead_code)]
542pub struct ComonadCtx<E, A> {
543    pub(super) env: E,
544    pub(super) focus: A,
545}
546#[allow(dead_code)]
547impl<E: Clone, A: Clone> ComonadCtx<E, A> {
548    /// Create a new comonad context.
549    pub fn new(env: E, focus: A) -> Self {
550        Self { env, focus }
551    }
552    /// Extract the focused value (comonad `extract`).
553    pub fn extract(&self) -> A {
554        self.focus.clone()
555    }
556    /// Duplicate: produce a context focused on itself.
557    pub fn duplicate(&self) -> ComonadCtx<E, ComonadCtx<E, A>> {
558        ComonadCtx {
559            env: self.env.clone(),
560            focus: self.clone(),
561        }
562    }
563    /// Extend: apply a function to the whole context, producing a new context.
564    pub fn extend<B, F: Fn(&ComonadCtx<E, A>) -> B>(&self, f: F) -> ComonadCtx<E, B> {
565        ComonadCtx {
566            env: self.env.clone(),
567            focus: f(self),
568        }
569    }
570    /// Map over the focused value.
571    pub fn map<B, F: FnOnce(A) -> B>(self, f: F) -> ComonadCtx<E, B> {
572        ComonadCtx {
573            env: self.env,
574            focus: f(self.focus),
575        }
576    }
577    /// Get a reference to the environment.
578    pub fn env(&self) -> &E {
579        &self.env
580    }
581}
582/// A pool of deferred computations indexed by `usize`.
583#[derive(Default)]
584pub struct DeferredPool<T> {
585    pending: std::collections::HashMap<usize, Box<dyn FnOnce() -> T>>,
586    resolved: std::collections::HashMap<usize, T>,
587    next_id: usize,
588}
589impl<T> DeferredPool<T> {
590    /// Create an empty pool.
591    pub fn new() -> Self {
592        Self {
593            pending: std::collections::HashMap::new(),
594            resolved: std::collections::HashMap::new(),
595            next_id: 0,
596        }
597    }
598    /// Submit a computation, returning its handle.
599    pub fn submit<F: FnOnce() -> T + 'static>(&mut self, f: F) -> usize {
600        let id = self.next_id;
601        self.next_id += 1;
602        self.pending.insert(id, Box::new(f));
603        id
604    }
605    /// Force a specific deferred computation.
606    pub fn force(&mut self, id: usize) -> Option<&T> {
607        if !self.resolved.contains_key(&id) {
608            if let Some(f) = self.pending.remove(&id) {
609                self.resolved.insert(id, f());
610            }
611        }
612        self.resolved.get(&id)
613    }
614    /// Force all pending computations.
615    pub fn force_all(&mut self) {
616        let ids: Vec<usize> = self.pending.keys().copied().collect();
617        for id in ids {
618            self.force(id);
619        }
620    }
621    /// Number of pending computations.
622    pub fn pending_count(&self) -> usize {
623        self.pending.len()
624    }
625    /// Number of resolved computations.
626    pub fn resolved_count(&self) -> usize {
627        self.resolved.len()
628    }
629}
630/// A fixed-point computation using memoized thunks.
631///
632/// Implements `fix f = f (fix f)` lazily: each call to `compute` evaluates
633/// one step, caching the result.  Useful for lazy recursive definitions.
634#[allow(clippy::type_complexity)]
635pub struct FixThunk<T: Clone> {
636    memo: std::collections::HashMap<usize, T>,
637    step: Box<dyn Fn(usize, &dyn Fn(usize) -> T) -> T>,
638}
639impl<T: Clone> FixThunk<T> {
640    /// Create a new fixed-point thunk.
641    ///
642    /// `f` receives the current index and a reference to `compute` so it can
643    /// look up previously computed values recursively.
644    pub fn new<F>(f: F) -> Self
645    where
646        F: Fn(usize, &dyn Fn(usize) -> T) -> T + 'static,
647    {
648        Self {
649            memo: std::collections::HashMap::new(),
650            step: Box::new(f),
651        }
652    }
653    /// Compute the value at index `n`, caching and reusing previous results.
654    pub fn compute(&mut self, n: usize) -> T {
655        if let Some(v) = self.memo.get(&n) {
656            return v.clone();
657        }
658        let memo_ref: *mut std::collections::HashMap<usize, T> = &mut self.memo;
659        let lookup = |k: usize| -> T {
660            unsafe { (*memo_ref).get(&k).cloned() }.expect("missing cached value")
661        };
662        let v = (self.step)(n, &lookup);
663        self.memo.insert(n, v.clone());
664        v
665    }
666}
667/// A memoized thunk that caches its result after first evaluation.
668#[allow(dead_code)]
669pub struct MemoThunk<T: Clone> {
670    cached: Option<T>,
671}
672#[allow(dead_code)]
673impl<T: Clone> MemoThunk<T> {
674    pub fn new() -> Self {
675        MemoThunk { cached: None }
676    }
677    pub fn force_with<F: FnOnce() -> T>(&mut self, f: F) -> T {
678        if let Some(ref val) = self.cached {
679            val.clone()
680        } else {
681            let val = f();
682            self.cached = Some(val.clone());
683            val
684        }
685    }
686    pub fn is_forced(&self) -> bool {
687        self.cached.is_some()
688    }
689    pub fn reset(&mut self) {
690        self.cached = None;
691    }
692}
693/// Lazy linked-list (stream) using closures for infinite sequences.
694#[allow(dead_code)]
695pub struct LazyLinkedStream<T> {
696    pub head: T,
697    pub tail_fn: Box<dyn Fn() -> Option<Box<LazyLinkedStream<T>>>>,
698}
699#[allow(dead_code)]
700impl<T: Clone + 'static> LazyLinkedStream<T> {
701    pub fn singleton(val: T) -> Self {
702        LazyLinkedStream {
703            head: val,
704            tail_fn: Box::new(|| None),
705        }
706    }
707    pub fn take_n(&self, n: usize) -> Vec<T> {
708        let mut result = vec![self.head.clone()];
709        let mut current = (self.tail_fn)();
710        while result.len() < n {
711            match current {
712                None => break,
713                Some(node) => {
714                    result.push(node.head.clone());
715                    current = (node.tail_fn)();
716                }
717            }
718        }
719        result
720    }
721}
722/// A value that is either immediately available or deferred.
723#[derive(Debug, Clone)]
724pub enum Deferred<T> {
725    /// Available immediately.
726    Now(T),
727    /// Will be computed later (opaque handle).
728    Later(usize),
729}
730impl<T: Clone> Deferred<T> {
731    /// Create an immediate value.
732    pub fn now(v: T) -> Self {
733        Self::Now(v)
734    }
735    /// Create a deferred handle.
736    pub fn later(id: usize) -> Self {
737        Self::Later(id)
738    }
739    /// Check if immediately available.
740    pub fn is_ready(&self) -> bool {
741        matches!(self, Deferred::Now(_))
742    }
743    /// Unwrap the immediate value, or call `f` with the handle.
744    pub fn resolve_or<F: FnOnce(usize) -> T>(self, f: F) -> T {
745        match self {
746            Deferred::Now(v) => v,
747            Deferred::Later(id) => f(id),
748        }
749    }
750    /// Map over an immediate value.
751    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Deferred<U> {
752        match self {
753            Deferred::Now(v) => Deferred::Now(f(v)),
754            Deferred::Later(id) => Deferred::Later(id),
755        }
756    }
757}
758/// An interaction tree node modelling one step of a potentially-infinite computation.
759///
760/// Based on the `ITree` structure used in verified concurrency frameworks.
761#[allow(dead_code)]
762pub enum ITreeNode<E, R> {
763    /// Pure return value.
764    Ret(R),
765    /// Silent step (Ï„): delays computation by one tick.
766    Tau(Box<dyn FnOnce() -> ITreeNode<E, R>>),
767    /// Visible event `e` followed by a continuation `k`.
768    Vis(E, Box<dyn Fn(usize) -> ITreeNode<E, R>>),
769}
770#[allow(dead_code)]
771impl<E: Clone, R: Clone> ITreeNode<E, R> {
772    /// Construct a pure return.
773    pub fn ret(r: R) -> Self {
774        ITreeNode::Ret(r)
775    }
776    /// Construct a tau step.
777    pub fn tau<F: FnOnce() -> ITreeNode<E, R> + 'static>(f: F) -> Self {
778        ITreeNode::Tau(Box::new(f))
779    }
780    /// Construct a visible event step.
781    pub fn vis<K: Fn(usize) -> ITreeNode<E, R> + 'static>(e: E, k: K) -> Self {
782        ITreeNode::Vis(e, Box::new(k))
783    }
784    /// Spin-up at most `fuel` tau steps and return the result if reached.
785    pub fn run(self, fuel: usize) -> Option<R> {
786        let mut current = self;
787        for _ in 0..fuel {
788            match current {
789                ITreeNode::Ret(r) => return Some(r),
790                ITreeNode::Tau(f) => {
791                    current = f();
792                }
793                ITreeNode::Vis(_, _) => return None,
794            }
795        }
796        None
797    }
798}