Skip to main content

oxilean_std/thunk/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::types::{
8    CofreeStream, ComonadCtx, ComputeNode, Deferred, DeferredPool, Delayed, GameArena, ITreeNode,
9    LazyList, LazyStream, Memo, MemoThunk, OnceCellThunk, Suspension, ThunkApp, ThunkKleisli,
10    ThunkSeq, ThunkState, ThunkTree, TryThunk,
11};
12
13pub fn type1() -> Expr {
14    Expr::Sort(Level::succ(Level::zero()))
15}
16pub fn type2() -> Expr {
17    Expr::Sort(Level::succ(Level::succ(Level::zero())))
18}
19pub fn thunk_of(alpha: Expr) -> Expr {
20    Expr::App(
21        Box::new(Expr::Const(Name::str("Thunk"), vec![])),
22        Box::new(alpha),
23    )
24}
25pub fn unit_ty() -> Expr {
26    Expr::Const(Name::str("Unit"), vec![])
27}
28pub fn bool_ty() -> Expr {
29    Expr::Const(Name::str("Bool"), vec![])
30}
31pub fn nat_ty() -> Expr {
32    Expr::Const(Name::str("Nat"), vec![])
33}
34pub fn list_of(alpha: Expr) -> Expr {
35    Expr::App(
36        Box::new(Expr::Const(Name::str("List"), vec![])),
37        Box::new(alpha),
38    )
39}
40pub fn unit_to(ret_ty: Expr) -> Expr {
41    Expr::Pi(
42        BinderInfo::Default,
43        Name::str("_"),
44        Box::new(unit_ty()),
45        Box::new(ret_ty),
46    )
47}
48pub fn option_of(alpha: Expr) -> Expr {
49    Expr::App(
50        Box::new(Expr::Const(Name::str("Option"), vec![])),
51        Box::new(alpha),
52    )
53}
54pub fn axiom(env: &mut Environment, name: &str, ty: Expr) -> Result<(), String> {
55    env.add(Declaration::Axiom {
56        name: Name::str(name),
57        univ_params: vec![],
58        ty,
59    })
60    .map_err(|e| e.to_string())
61}
62pub fn alpha_implicit(inner: Expr) -> Expr {
63    Expr::Pi(
64        BinderInfo::Implicit,
65        Name::str("α"),
66        Box::new(type1()),
67        Box::new(inner),
68    )
69}
70pub fn ab_implicit(inner: Expr) -> Expr {
71    Expr::Pi(
72        BinderInfo::Implicit,
73        Name::str("α"),
74        Box::new(type1()),
75        Box::new(Expr::Pi(
76            BinderInfo::Implicit,
77            Name::str("β"),
78            Box::new(type1()),
79            Box::new(inner),
80        )),
81    )
82}
83/// Build Thunk type in the environment.
84pub fn build_thunk_env(env: &mut Environment) -> Result<(), String> {
85    let thunk_ty = Expr::Pi(
86        BinderInfo::Default,
87        Name::str("α"),
88        Box::new(type1()),
89        Box::new(type2()),
90    );
91    env.add(Declaration::Axiom {
92        name: Name::str("Thunk"),
93        univ_params: vec![],
94        ty: thunk_ty,
95    })
96    .map_err(|e| e.to_string())?;
97    add_mk(env)?;
98    add_get(env)?;
99    add_pure(env)?;
100    add_map(env)?;
101    add_bind(env)?;
102    add_join(env)?;
103    add_zip(env)?;
104    add_and(env)?;
105    add_or(env)?;
106    add_is_forced(env)?;
107    add_ap(env)?;
108    add_sequence(env)?;
109    add_delay(env)?;
110    add_count(env)?;
111    add_get_or(env)?;
112    Ok(())
113}
114pub fn add_mk(env: &mut Environment) -> Result<(), String> {
115    let ty = alpha_implicit(Expr::Pi(
116        BinderInfo::Default,
117        Name::str("f"),
118        Box::new(unit_to(Expr::BVar(1))),
119        Box::new(thunk_of(Expr::BVar(1))),
120    ));
121    axiom(env, "Thunk.mk", ty)
122}
123pub fn add_get(env: &mut Environment) -> Result<(), String> {
124    let ty = alpha_implicit(Expr::Pi(
125        BinderInfo::Default,
126        Name::str("t"),
127        Box::new(thunk_of(Expr::BVar(0))),
128        Box::new(Expr::BVar(1)),
129    ));
130    axiom(env, "Thunk.get", ty)
131}
132pub fn add_pure(env: &mut Environment) -> Result<(), String> {
133    let ty = alpha_implicit(Expr::Pi(
134        BinderInfo::Default,
135        Name::str("x"),
136        Box::new(Expr::BVar(0)),
137        Box::new(thunk_of(Expr::BVar(1))),
138    ));
139    axiom(env, "Thunk.pure", ty)
140}
141pub fn add_map(env: &mut Environment) -> Result<(), String> {
142    let fn_ty = Expr::Pi(
143        BinderInfo::Default,
144        Name::str("_"),
145        Box::new(Expr::BVar(1)),
146        Box::new(Expr::BVar(1)),
147    );
148    let ty = ab_implicit(Expr::Pi(
149        BinderInfo::Default,
150        Name::str("f"),
151        Box::new(fn_ty),
152        Box::new(Expr::Pi(
153            BinderInfo::Default,
154            Name::str("t"),
155            Box::new(thunk_of(Expr::BVar(2))),
156            Box::new(thunk_of(Expr::BVar(2))),
157        )),
158    ));
159    axiom(env, "Thunk.map", ty)
160}
161pub fn add_bind(env: &mut Environment) -> Result<(), String> {
162    let fn_ty = Expr::Pi(
163        BinderInfo::Default,
164        Name::str("_"),
165        Box::new(Expr::BVar(1)),
166        Box::new(thunk_of(Expr::BVar(1))),
167    );
168    let ty = ab_implicit(Expr::Pi(
169        BinderInfo::Default,
170        Name::str("t"),
171        Box::new(thunk_of(Expr::BVar(1))),
172        Box::new(Expr::Pi(
173            BinderInfo::Default,
174            Name::str("f"),
175            Box::new(fn_ty),
176            Box::new(thunk_of(Expr::BVar(2))),
177        )),
178    ));
179    axiom(env, "Thunk.bind", ty)
180}
181pub fn add_join(env: &mut Environment) -> Result<(), String> {
182    let ty = alpha_implicit(Expr::Pi(
183        BinderInfo::Default,
184        Name::str("t"),
185        Box::new(thunk_of(thunk_of(Expr::BVar(0)))),
186        Box::new(thunk_of(Expr::BVar(1))),
187    ));
188    axiom(env, "Thunk.join", ty)
189}
190pub fn add_zip(env: &mut Environment) -> Result<(), String> {
191    let prod_ty = Expr::App(
192        Box::new(Expr::App(
193            Box::new(Expr::Const(Name::str("Prod"), vec![])),
194            Box::new(Expr::BVar(1)),
195        )),
196        Box::new(Expr::BVar(0)),
197    );
198    let ty = ab_implicit(Expr::Pi(
199        BinderInfo::Default,
200        Name::str("ta"),
201        Box::new(thunk_of(Expr::BVar(1))),
202        Box::new(Expr::Pi(
203            BinderInfo::Default,
204            Name::str("tb"),
205            Box::new(thunk_of(Expr::BVar(1))),
206            Box::new(thunk_of(prod_ty)),
207        )),
208    ));
209    axiom(env, "Thunk.zip", ty)
210}
211pub fn add_bool_op(env: &mut Environment, name: &str) -> Result<(), String> {
212    let ty = Expr::Pi(
213        BinderInfo::Default,
214        Name::str("a"),
215        Box::new(thunk_of(bool_ty())),
216        Box::new(Expr::Pi(
217            BinderInfo::Default,
218            Name::str("b"),
219            Box::new(thunk_of(bool_ty())),
220            Box::new(thunk_of(bool_ty())),
221        )),
222    );
223    axiom(env, name, ty)
224}
225pub fn add_and(env: &mut Environment) -> Result<(), String> {
226    add_bool_op(env, "Thunk.and")
227}
228pub fn add_or(env: &mut Environment) -> Result<(), String> {
229    add_bool_op(env, "Thunk.or")
230}
231pub fn add_is_forced(env: &mut Environment) -> Result<(), String> {
232    let ty = alpha_implicit(Expr::Pi(
233        BinderInfo::Default,
234        Name::str("t"),
235        Box::new(thunk_of(Expr::BVar(0))),
236        Box::new(bool_ty()),
237    ));
238    axiom(env, "Thunk.isForced", ty)
239}
240pub fn add_ap(env: &mut Environment) -> Result<(), String> {
241    let fn_ty = Expr::Pi(
242        BinderInfo::Default,
243        Name::str("_"),
244        Box::new(Expr::BVar(1)),
245        Box::new(Expr::BVar(1)),
246    );
247    let ty = ab_implicit(Expr::Pi(
248        BinderInfo::Default,
249        Name::str("tf"),
250        Box::new(thunk_of(fn_ty)),
251        Box::new(Expr::Pi(
252            BinderInfo::Default,
253            Name::str("ta"),
254            Box::new(thunk_of(Expr::BVar(2))),
255            Box::new(thunk_of(Expr::BVar(2))),
256        )),
257    ));
258    axiom(env, "Thunk.ap", ty)
259}
260pub fn add_sequence(env: &mut Environment) -> Result<(), String> {
261    let ty = alpha_implicit(Expr::Pi(
262        BinderInfo::Default,
263        Name::str("ts"),
264        Box::new(list_of(thunk_of(Expr::BVar(0)))),
265        Box::new(thunk_of(list_of(Expr::BVar(1)))),
266    ));
267    axiom(env, "Thunk.sequence", ty)
268}
269pub fn add_delay(env: &mut Environment) -> Result<(), String> {
270    let ty = alpha_implicit(Expr::Pi(
271        BinderInfo::Default,
272        Name::str("x"),
273        Box::new(Expr::BVar(0)),
274        Box::new(thunk_of(Expr::BVar(1))),
275    ));
276    axiom(env, "Thunk.delay", ty)
277}
278pub fn add_count(env: &mut Environment) -> Result<(), String> {
279    let ty = alpha_implicit(Expr::Pi(
280        BinderInfo::Default,
281        Name::str("ts"),
282        Box::new(list_of(thunk_of(Expr::BVar(0)))),
283        Box::new(nat_ty()),
284    ));
285    axiom(env, "Thunk.count", ty)
286}
287pub fn add_get_or(env: &mut Environment) -> Result<(), String> {
288    let ty = alpha_implicit(Expr::Pi(
289        BinderInfo::Default,
290        Name::str("opt"),
291        Box::new(option_of(thunk_of(Expr::BVar(0)))),
292        Box::new(Expr::Pi(
293            BinderInfo::Default,
294            Name::str("def"),
295            Box::new(Expr::BVar(1)),
296            Box::new(Expr::BVar(2)),
297        )),
298    ));
299    axiom(env, "Thunk.getOr", ty)
300}
301pub fn setup_base_env() -> Environment {
302    let mut env = Environment::new();
303    let t1 = type1();
304    for name in ["Unit", "Bool", "Prod", "List", "Nat", "Option"] {
305        env.add(Declaration::Axiom {
306            name: Name::str(name),
307            univ_params: vec![],
308            ty: t1.clone(),
309        })
310        .unwrap_or(());
311    }
312    env
313}
314#[cfg(test)]
315mod tests {
316    use super::*;
317    #[test]
318    fn test_build_thunk_env() {
319        let mut env = setup_base_env();
320        assert!(build_thunk_env(&mut env).is_ok());
321        assert!(env.get(&Name::str("Thunk")).is_some());
322        assert!(env.get(&Name::str("Thunk.mk")).is_some());
323        assert!(env.get(&Name::str("Thunk.get")).is_some());
324    }
325    #[test]
326    fn test_thunk_pure() {
327        let mut env = setup_base_env();
328        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
329        assert!(matches!(
330            env.get(&Name::str("Thunk.pure"))
331                .expect("declaration 'Thunk.pure' should exist in env"),
332            Declaration::Axiom { .. }
333        ));
334    }
335    #[test]
336    fn test_thunk_map() {
337        let mut env = setup_base_env();
338        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
339        assert!(matches!(
340            env.get(&Name::str("Thunk.map"))
341                .expect("declaration 'Thunk.map' should exist in env"),
342            Declaration::Axiom { .. }
343        ));
344    }
345    #[test]
346    fn test_thunk_bind() {
347        let mut env = setup_base_env();
348        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
349        assert!(env.get(&Name::str("Thunk.bind")).is_some());
350    }
351    #[test]
352    fn test_thunk_join() {
353        let mut env = setup_base_env();
354        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
355        assert!(env.get(&Name::str("Thunk.join")).is_some());
356    }
357    #[test]
358    fn test_thunk_zip() {
359        let mut env = setup_base_env();
360        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
361        assert!(env.get(&Name::str("Thunk.zip")).is_some());
362    }
363    #[test]
364    fn test_thunk_and() {
365        let mut env = setup_base_env();
366        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
367        assert!(env.get(&Name::str("Thunk.and")).is_some());
368    }
369    #[test]
370    fn test_thunk_or() {
371        let mut env = setup_base_env();
372        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
373        assert!(env.get(&Name::str("Thunk.or")).is_some());
374    }
375    #[test]
376    fn test_thunk_is_forced() {
377        let mut env = setup_base_env();
378        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
379        assert!(env.get(&Name::str("Thunk.isForced")).is_some());
380    }
381    #[test]
382    fn test_thunk_mk_is_axiom() {
383        let mut env = setup_base_env();
384        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
385        assert!(matches!(
386            env.get(&Name::str("Thunk.mk"))
387                .expect("declaration 'Thunk.mk' should exist in env"),
388            Declaration::Axiom { .. }
389        ));
390    }
391    #[test]
392    fn test_thunk_ap() {
393        let mut env = setup_base_env();
394        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
395        assert!(env.get(&Name::str("Thunk.ap")).is_some());
396    }
397    #[test]
398    fn test_thunk_sequence() {
399        let mut env = setup_base_env();
400        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
401        assert!(env.get(&Name::str("Thunk.sequence")).is_some());
402    }
403    #[test]
404    fn test_thunk_delay() {
405        let mut env = setup_base_env();
406        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
407        assert!(env.get(&Name::str("Thunk.delay")).is_some());
408    }
409    #[test]
410    fn test_thunk_count() {
411        let mut env = setup_base_env();
412        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
413        assert!(env.get(&Name::str("Thunk.count")).is_some());
414    }
415    #[test]
416    fn test_thunk_get_or() {
417        let mut env = setup_base_env();
418        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
419        assert!(env.get(&Name::str("Thunk.getOr")).is_some());
420    }
421}
422#[cfg(test)]
423mod thunk_extended_tests {
424    use super::*;
425    #[test]
426    fn test_once_cell_thunk_forces_once() {
427        let count = std::sync::Arc::new(std::sync::Mutex::new(0));
428        let count_clone = count.clone();
429        let mut thunk = OnceCellThunk::new(move || {
430            *count_clone.lock().expect("lock should succeed") += 1;
431            42i32
432        });
433        assert!(!thunk.is_forced());
434        assert_eq!(*thunk.force(), 42);
435        assert!(thunk.is_forced());
436        assert_eq!(*thunk.force(), 42);
437        assert_eq!(*count.lock().expect("lock should succeed"), 1);
438    }
439    #[test]
440    fn test_thunk_seq_lazy() {
441        let mut seq: ThunkSeq<i32> = ThunkSeq::new();
442        seq.push(|| 10);
443        seq.push(|| 20);
444        seq.push(|| 30);
445        assert_eq!(seq.len(), 3);
446        assert_eq!(seq.forced_count(), 0);
447        assert_eq!(seq.get(1), Some(&20));
448        assert_eq!(seq.forced_count(), 1);
449    }
450    #[test]
451    fn test_thunk_seq_out_of_bounds() {
452        let mut seq: ThunkSeq<i32> = ThunkSeq::new();
453        seq.push(|| 1);
454        assert!(seq.get(5).is_none());
455    }
456    #[test]
457    fn test_memo_caches_result() {
458        let call_count = std::sync::Arc::new(std::sync::Mutex::new(0usize));
459        let cc = call_count.clone();
460        let mut memo = Memo::new(move |x: &i32| {
461            *cc.lock().expect("lock should succeed") += 1;
462            x * 2
463        });
464        assert_eq!(*memo.call(&5), 10);
465        assert_eq!(*memo.call(&5), 10);
466        assert_eq!(*call_count.lock().expect("lock should succeed"), 1);
467        assert_eq!(memo.cache_size(), 1);
468    }
469    #[test]
470    fn test_lazy_list_finite() {
471        let mut list = LazyList::from_fn(|i| if i < 5 { Some(i * i) } else { None });
472        let first3 = list.take(3);
473        assert_eq!(first3, vec![0, 1, 4]);
474    }
475    #[test]
476    fn test_lazy_list_get() {
477        let mut list = LazyList::from_fn(|i| Some(i as i32 + 1));
478        assert_eq!(list.get(0), Some(&1));
479        assert_eq!(list.get(4), Some(&5));
480    }
481    #[test]
482    fn test_thunk_tree_leaf() {
483        let t = ThunkTree::leaf(42i32);
484        assert!(t.is_leaf());
485        let leaves = t.leaves();
486        assert_eq!(leaves, vec![42]);
487    }
488    #[test]
489    fn test_thunk_tree_node() {
490        let t: ThunkTree<i32> = ThunkTree::node(|| vec![ThunkTree::leaf(1), ThunkTree::leaf(2)]);
491        assert!(!t.is_leaf());
492        let leaves = t.leaves();
493        assert_eq!(leaves.len(), 2);
494    }
495    #[test]
496    fn test_memo_clear_cache() {
497        let mut memo = Memo::new(|x: &i32| x + 1);
498        memo.call(&1);
499        memo.call(&2);
500        assert_eq!(memo.cache_size(), 2);
501        memo.clear_cache();
502        assert_eq!(memo.cache_size(), 0);
503    }
504}
505#[cfg(test)]
506mod thunk_deferred_tests {
507    use super::*;
508    #[test]
509    fn test_try_thunk_ok() {
510        let mut t: TryThunk<i32, &str> = TryThunk::new(|| Ok(42));
511        assert!(!t.is_forced());
512        assert_eq!(t.force(), &Ok(42));
513        assert!(t.is_forced());
514    }
515    #[test]
516    fn test_try_thunk_err() {
517        let mut t: TryThunk<i32, &str> = TryThunk::new(|| Err("fail"));
518        assert_eq!(t.force(), &Err("fail"));
519    }
520    #[test]
521    fn test_deferred_now_is_ready() {
522        let d = Deferred::now(10i32);
523        assert!(d.is_ready());
524    }
525    #[test]
526    fn test_deferred_later_not_ready() {
527        let d: Deferred<i32> = Deferred::later(0);
528        assert!(!d.is_ready());
529    }
530    #[test]
531    fn test_deferred_resolve_or() {
532        let d = Deferred::now(5i32);
533        assert_eq!(d.resolve_or(|_| 99), 5);
534        let d2: Deferred<i32> = Deferred::later(7);
535        assert_eq!(d2.resolve_or(|id| id as i32), 7);
536    }
537    #[test]
538    fn test_deferred_map() {
539        let d = Deferred::now(3i32);
540        let d2 = d.map(|x| x * 2);
541        assert_eq!(d2.resolve_or(|_| 0), 6);
542    }
543    #[test]
544    fn test_deferred_pool_submit_force() {
545        let mut pool = DeferredPool::new();
546        let id = pool.submit(|| 42i32);
547        assert_eq!(pool.pending_count(), 1);
548        let v = pool.force(id);
549        assert_eq!(v, Some(&42));
550        assert_eq!(pool.resolved_count(), 1);
551        assert_eq!(pool.pending_count(), 0);
552    }
553    #[test]
554    fn test_deferred_pool_force_all() {
555        let mut pool = DeferredPool::new();
556        pool.submit(|| 1i32);
557        pool.submit(|| 2i32);
558        pool.force_all();
559        assert_eq!(pool.pending_count(), 0);
560        assert_eq!(pool.resolved_count(), 2);
561    }
562    #[test]
563    fn test_delayed_steps() {
564        let mut d = Delayed::new(42i32, 3);
565        assert!(!d.is_ready());
566        assert_eq!(d.step(), None);
567        assert_eq!(d.step(), None);
568        assert_eq!(d.step(), Some(42));
569        assert!(d.is_ready());
570    }
571    #[test]
572    fn test_delayed_remaining() {
573        let d = Delayed::new(1i32, 5);
574        assert_eq!(d.remaining(), 5);
575    }
576}
577/// Build Thunk theorems in the environment as axioms.
578///
579/// Adds basic thunk laws: `Thunk.get_pure`, `Thunk.map_id`, `Thunk.map_comp`.
580pub fn build_thunk_theorems(env: &mut Environment) -> Result<(), String> {
581    let prop = Expr::Sort(Level::zero());
582    let type1 = Expr::Sort(Level::succ(Level::zero()));
583    let get_pure_ty = Expr::Pi(
584        BinderInfo::Implicit,
585        Name::str("α"),
586        Box::new(type1.clone()),
587        Box::new(Expr::Pi(
588            BinderInfo::Default,
589            Name::str("x"),
590            Box::new(Expr::BVar(0)),
591            Box::new(prop.clone()),
592        )),
593    );
594    env.add(Declaration::Axiom {
595        name: Name::str("Thunk.get_pure"),
596        univ_params: vec![],
597        ty: get_pure_ty,
598    })
599    .map_err(|e| e.to_string())?;
600    let map_id_ty = Expr::Pi(
601        BinderInfo::Implicit,
602        Name::str("α"),
603        Box::new(type1.clone()),
604        Box::new(Expr::Pi(
605            BinderInfo::Default,
606            Name::str("t"),
607            Box::new(Expr::App(
608                Box::new(Expr::Const(Name::str("Thunk"), vec![])),
609                Box::new(Expr::BVar(0)),
610            )),
611            Box::new(prop.clone()),
612        )),
613    );
614    env.add(Declaration::Axiom {
615        name: Name::str("Thunk.map_id"),
616        univ_params: vec![],
617        ty: map_id_ty,
618    })
619    .map_err(|e| e.to_string())?;
620    let bind_pure_ty = Expr::Pi(
621        BinderInfo::Implicit,
622        Name::str("α"),
623        Box::new(type1.clone()),
624        Box::new(Expr::Pi(
625            BinderInfo::Default,
626            Name::str("x"),
627            Box::new(Expr::BVar(0)),
628            Box::new(prop.clone()),
629        )),
630    );
631    env.add(Declaration::Axiom {
632        name: Name::str("Thunk.bind_pure"),
633        univ_params: vec![],
634        ty: bind_pure_ty,
635    })
636    .map_err(|e| e.to_string())?;
637    Ok(())
638}
639/// Count how many Thunk declarations are registered in the environment.
640pub fn count_thunk_decls(env: &Environment) -> usize {
641    let names = [
642        "Thunk",
643        "Thunk.mk",
644        "Thunk.get",
645        "Thunk.pure",
646        "Thunk.map",
647        "Thunk.bind",
648        "Thunk.join",
649        "Thunk.zip",
650        "Thunk.and",
651        "Thunk.or",
652        "Thunk.isForced",
653        "Thunk.ap",
654        "Thunk.sequence",
655        "Thunk.delay",
656        "Thunk.count",
657        "Thunk.getOr",
658        "Thunk.get_pure",
659        "Thunk.map_id",
660        "Thunk.bind_pure",
661    ];
662    names
663        .iter()
664        .filter(|&&n| env.get(&Name::str(n)).is_some())
665        .count()
666}
667#[cfg(test)]
668mod thunk_extended_extra_tests {
669    use super::*;
670    fn base() -> Environment {
671        let mut env = Environment::new();
672        let t1 = Expr::Sort(Level::succ(Level::zero()));
673        for name in ["Unit", "Bool", "Prod", "List", "Nat", "Option"] {
674            env.add(Declaration::Axiom {
675                name: Name::str(name),
676                univ_params: vec![],
677                ty: t1.clone(),
678            })
679            .unwrap_or(());
680        }
681        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
682        env
683    }
684    #[test]
685    fn test_build_thunk_theorems() {
686        let mut env = base();
687        assert!(build_thunk_theorems(&mut env).is_ok());
688        assert!(env.get(&Name::str("Thunk.get_pure")).is_some());
689    }
690    #[test]
691    fn test_thunk_map_id() {
692        let mut env = base();
693        build_thunk_theorems(&mut env).expect("build_thunk_theorems should succeed");
694        assert!(env.get(&Name::str("Thunk.map_id")).is_some());
695    }
696    #[test]
697    fn test_thunk_bind_pure() {
698        let mut env = base();
699        build_thunk_theorems(&mut env).expect("build_thunk_theorems should succeed");
700        assert!(env.get(&Name::str("Thunk.bind_pure")).is_some());
701    }
702    #[test]
703    fn test_count_thunk_decls_base() {
704        let env = base();
705        let n = count_thunk_decls(&env);
706        assert!(n >= 16);
707    }
708    #[test]
709    fn test_count_thunk_decls_with_theorems() {
710        let mut env = base();
711        build_thunk_theorems(&mut env).expect("build_thunk_theorems should succeed");
712        let n = count_thunk_decls(&env);
713        assert!(n >= 19);
714    }
715    #[test]
716    fn test_deferred_pool_missing_id() {
717        let mut pool = DeferredPool::<i32>::new();
718        let v = pool.force(999);
719        assert!(v.is_none());
720    }
721    #[test]
722    fn test_thunk_seq_is_empty() {
723        let seq: ThunkSeq<i32> = ThunkSeq::new();
724        assert!(seq.is_empty());
725    }
726    #[test]
727    fn test_lazy_list_produced_count() {
728        let mut list = LazyList::from_fn(|i| Some(i as i32));
729        list.take(5);
730        assert_eq!(list.produced_count(), 5);
731    }
732}
733pub fn thk_ext_prop() -> Expr {
734    Expr::Sort(Level::zero())
735}
736pub fn thk_ext_ab_prop(body: Expr) -> Expr {
737    Expr::Pi(
738        BinderInfo::Implicit,
739        Name::str("α"),
740        Box::new(type1()),
741        Box::new(Expr::Pi(
742            BinderInfo::Implicit,
743            Name::str("β"),
744            Box::new(type1()),
745            Box::new(body),
746        )),
747    )
748}
749pub fn thk_ext_abc_implicit(body: Expr) -> Expr {
750    Expr::Pi(
751        BinderInfo::Implicit,
752        Name::str("α"),
753        Box::new(type1()),
754        Box::new(Expr::Pi(
755            BinderInfo::Implicit,
756            Name::str("β"),
757            Box::new(type1()),
758            Box::new(Expr::Pi(
759                BinderInfo::Implicit,
760                Name::str("γ"),
761                Box::new(type1()),
762                Box::new(body),
763            )),
764        )),
765    )
766}
767/// `Thunk.extract : {α : Type} → Thunk α → α`
768///
769/// The comonad extract (ε) operation: forces and returns the value.
770pub fn axiom_comonad_extract_ty() -> Expr {
771    alpha_implicit(Expr::Pi(
772        BinderInfo::Default,
773        Name::str("t"),
774        Box::new(thunk_of(Expr::BVar(0))),
775        Box::new(Expr::BVar(1)),
776    ))
777}
778/// `Thunk.duplicate : {α : Type} → Thunk α → Thunk (Thunk α)`
779///
780/// The comonad duplicate (δ) operation.
781pub fn axiom_comonad_duplicate_ty() -> Expr {
782    alpha_implicit(Expr::Pi(
783        BinderInfo::Default,
784        Name::str("t"),
785        Box::new(thunk_of(Expr::BVar(0))),
786        Box::new(thunk_of(thunk_of(Expr::BVar(1)))),
787    ))
788}
789/// `Thunk.extend : {α β : Type} → (Thunk α → β) → Thunk α → Thunk β`
790///
791/// Cokleisli extension: the comonad co-bind operation.
792pub fn axiom_comonad_extend_ty() -> Expr {
793    let coalg = Expr::Pi(
794        BinderInfo::Default,
795        Name::str("_"),
796        Box::new(thunk_of(Expr::BVar(1))),
797        Box::new(Expr::BVar(1)),
798    );
799    ab_implicit(Expr::Pi(
800        BinderInfo::Default,
801        Name::str("f"),
802        Box::new(coalg),
803        Box::new(Expr::Pi(
804            BinderInfo::Default,
805            Name::str("t"),
806            Box::new(thunk_of(Expr::BVar(2))),
807            Box::new(thunk_of(Expr::BVar(2))),
808        )),
809    ))
810}
811/// `Thunk.extract_duplicate : {α : Type} → ∀ t : Thunk α, extract (duplicate t) = t`
812///
813/// First comonad law: extract ∘ duplicate = id.
814pub fn axiom_comonad_extract_duplicate_ty() -> Expr {
815    alpha_implicit(Expr::Pi(
816        BinderInfo::Default,
817        Name::str("t"),
818        Box::new(thunk_of(Expr::BVar(0))),
819        Box::new(thk_ext_prop()),
820    ))
821}
822/// `Thunk.duplicate_extract : {α : Type} → ∀ t, map extract (duplicate t) = t`
823///
824/// Second comonad law: map extract ∘ duplicate = id.
825pub fn axiom_comonad_duplicate_extract_ty() -> Expr {
826    alpha_implicit(Expr::Pi(
827        BinderInfo::Default,
828        Name::str("t"),
829        Box::new(thunk_of(Expr::BVar(0))),
830        Box::new(thk_ext_prop()),
831    ))
832}
833/// `Thunk.duplicate_duplicate : {α : Type} → ∀ t, duplicate (duplicate t) = map duplicate (duplicate t)`
834///
835/// Third comonad law: duplicate ∘ duplicate = map duplicate ∘ duplicate.
836pub fn axiom_comonad_duplicate_duplicate_ty() -> Expr {
837    alpha_implicit(Expr::Pi(
838        BinderInfo::Default,
839        Name::str("t"),
840        Box::new(thunk_of(Expr::BVar(0))),
841        Box::new(thk_ext_prop()),
842    ))
843}
844/// `Thunk.cokleisli_compose : {α β γ : Type} → (Thunk α → β) → (Thunk β → γ) → Thunk α → γ`
845///
846/// Cokleisli composition in the Thunk comonad.
847pub fn axiom_cokleisli_compose_ty() -> Expr {
848    let f_ty = Expr::Pi(
849        BinderInfo::Default,
850        Name::str("_"),
851        Box::new(thunk_of(Expr::BVar(2))),
852        Box::new(Expr::BVar(2)),
853    );
854    let g_ty = Expr::Pi(
855        BinderInfo::Default,
856        Name::str("_"),
857        Box::new(thunk_of(Expr::BVar(2))),
858        Box::new(Expr::BVar(2)),
859    );
860    thk_ext_abc_implicit(Expr::Pi(
861        BinderInfo::Default,
862        Name::str("f"),
863        Box::new(f_ty),
864        Box::new(Expr::Pi(
865            BinderInfo::Default,
866            Name::str("g"),
867            Box::new(g_ty),
868            Box::new(Expr::Pi(
869                BinderInfo::Default,
870                Name::str("t"),
871                Box::new(thunk_of(Expr::BVar(4))),
872                Box::new(Expr::BVar(4)),
873            )),
874        )),
875    ))
876}
877/// `Thunk.map_comp : {α β γ : Type} → (β → γ) → (α → β) → Thunk α → Thunk γ`
878///
879/// Functor composition law: map (g ∘ f) = map g ∘ map f.
880pub fn axiom_functor_map_comp_ty() -> Expr {
881    let f_ty = Expr::Pi(
882        BinderInfo::Default,
883        Name::str("_"),
884        Box::new(Expr::BVar(2)),
885        Box::new(Expr::BVar(2)),
886    );
887    let g_ty = Expr::Pi(
888        BinderInfo::Default,
889        Name::str("_"),
890        Box::new(Expr::BVar(2)),
891        Box::new(Expr::BVar(2)),
892    );
893    thk_ext_abc_implicit(Expr::Pi(
894        BinderInfo::Default,
895        Name::str("g"),
896        Box::new(g_ty),
897        Box::new(Expr::Pi(
898            BinderInfo::Default,
899            Name::str("f"),
900            Box::new(f_ty),
901            Box::new(Expr::Pi(
902                BinderInfo::Default,
903                Name::str("t"),
904                Box::new(thunk_of(Expr::BVar(4))),
905                Box::new(thunk_of(Expr::BVar(4))),
906            )),
907        )),
908    ))
909}
910/// `Thunk.get_mk : {α : Type} → ∀ f : Unit → α, get (mk f) = f ()`
911///
912/// Forcing a constructed thunk returns the applied value.
913pub fn axiom_get_mk_ty() -> Expr {
914    alpha_implicit(Expr::Pi(
915        BinderInfo::Default,
916        Name::str("f"),
917        Box::new(unit_to(Expr::BVar(1))),
918        Box::new(thk_ext_prop()),
919    ))
920}
921/// `Thunk.mk_get : {α : Type} → ∀ t : Thunk α, mk (fun _ => get t) = t`
922///
923/// Reconstructing a thunk from its forced value equals the original.
924pub fn axiom_mk_get_ty() -> Expr {
925    alpha_implicit(Expr::Pi(
926        BinderInfo::Default,
927        Name::str("t"),
928        Box::new(thunk_of(Expr::BVar(0))),
929        Box::new(thk_ext_prop()),
930    ))
931}
932/// `Thunk.sharing : {α : Type} → ∀ t : Thunk α, get t = get t`
933///
934/// Sharing / memoization correctness: forcing a thunk twice gives the same result.
935pub fn axiom_sharing_ty() -> Expr {
936    alpha_implicit(Expr::Pi(
937        BinderInfo::Default,
938        Name::str("t"),
939        Box::new(thunk_of(Expr::BVar(0))),
940        Box::new(thk_ext_prop()),
941    ))
942}
943/// `Thunk.force_once : {α : Type} → ∀ t : Thunk α, ∀ _ : Bool, isForced t = true → get t = get t`
944///
945/// After the first force, the cached result equals re-forcing.
946pub fn axiom_force_once_ty() -> Expr {
947    alpha_implicit(Expr::Pi(
948        BinderInfo::Default,
949        Name::str("t"),
950        Box::new(thunk_of(Expr::BVar(0))),
951        Box::new(Expr::Pi(
952            BinderInfo::Default,
953            Name::str("_h"),
954            Box::new(bool_ty()),
955            Box::new(thk_ext_prop()),
956        )),
957    ))
958}
959/// `Thunk.cbn_beta : {α : Type} → ∀ (f : Unit → α), get (mk f) = f ()`
960///
961/// Call-by-need β-reduction: evaluating the thunk equals applying the function.
962pub fn axiom_cbn_beta_ty() -> Expr {
963    alpha_implicit(Expr::Pi(
964        BinderInfo::Default,
965        Name::str("f"),
966        Box::new(unit_to(Expr::BVar(1))),
967        Box::new(thk_ext_prop()),
968    ))
969}
970/// `Thunk.presheaf_restriction : {α β : Type} → (β → α) → Thunk α → Thunk β`
971///
972/// Thunk as a presheaf on ω: restriction maps.
973pub fn axiom_presheaf_restriction_ty() -> Expr {
974    let f_ty = Expr::Pi(
975        BinderInfo::Default,
976        Name::str("_"),
977        Box::new(Expr::BVar(0)),
978        Box::new(Expr::BVar(1)),
979    );
980    ab_implicit(Expr::Pi(
981        BinderInfo::Default,
982        Name::str("r"),
983        Box::new(f_ty),
984        Box::new(Expr::Pi(
985            BinderInfo::Default,
986            Name::str("t"),
987            Box::new(thunk_of(Expr::BVar(2))),
988            Box::new(thunk_of(Expr::BVar(2))),
989        )),
990    ))
991}
992/// `Thunk.productive_step : {α : Type} → (Nat → Thunk α) → Nat → Thunk α`
993///
994/// Productive corecursion via thunks: each step produces a value.
995pub fn axiom_productive_step_ty() -> Expr {
996    let step_fn = Expr::Pi(
997        BinderInfo::Default,
998        Name::str("_"),
999        Box::new(nat_ty()),
1000        Box::new(thunk_of(Expr::BVar(1))),
1001    );
1002    alpha_implicit(Expr::Pi(
1003        BinderInfo::Default,
1004        Name::str("gen"),
1005        Box::new(step_fn),
1006        Box::new(Expr::Pi(
1007            BinderInfo::Default,
1008            Name::str("n"),
1009            Box::new(nat_ty()),
1010            Box::new(thunk_of(Expr::BVar(2))),
1011        )),
1012    ))
1013}
1014/// `Thunk.guarded_next : {α : Type} → Thunk α → Thunk α`
1015///
1016/// Guarded recursion: the next step in a guarded recursive thunk.
1017pub fn axiom_guarded_next_ty() -> Expr {
1018    alpha_implicit(Expr::Pi(
1019        BinderInfo::Default,
1020        Name::str("t"),
1021        Box::new(thunk_of(Expr::BVar(0))),
1022        Box::new(thunk_of(Expr::BVar(1))),
1023    ))
1024}
1025/// `Thunk.game_question : {α : Type} → Thunk α → Nat`
1026///
1027/// Game semantics: the "question" move associated with a thunk.
1028pub fn axiom_game_question_ty() -> Expr {
1029    alpha_implicit(Expr::Pi(
1030        BinderInfo::Default,
1031        Name::str("t"),
1032        Box::new(thunk_of(Expr::BVar(0))),
1033        Box::new(nat_ty()),
1034    ))
1035}
1036/// `Thunk.game_answer : {α : Type} → Thunk α → α`
1037///
1038/// Game semantics: the "answer" move, equivalent to forcing.
1039pub fn axiom_game_answer_ty() -> Expr {
1040    alpha_implicit(Expr::Pi(
1041        BinderInfo::Default,
1042        Name::str("t"),
1043        Box::new(thunk_of(Expr::BVar(0))),
1044        Box::new(Expr::BVar(1)),
1045    ))
1046}
1047/// `Thunk.itree_ret : {α : Type} → α → Thunk α`
1048///
1049/// Interaction tree return: a pure leaf in an itree encoded as a thunk.
1050pub fn axiom_itree_ret_ty() -> Expr {
1051    alpha_implicit(Expr::Pi(
1052        BinderInfo::Default,
1053        Name::str("x"),
1054        Box::new(Expr::BVar(0)),
1055        Box::new(thunk_of(Expr::BVar(1))),
1056    ))
1057}
1058/// `Thunk.itree_tau : {α : Type} → Thunk α → Thunk α`
1059///
1060/// Interaction tree silent step (Ï„): wraps a thunk in one more layer of delay.
1061pub fn axiom_itree_tau_ty() -> Expr {
1062    alpha_implicit(Expr::Pi(
1063        BinderInfo::Default,
1064        Name::str("t"),
1065        Box::new(thunk_of(Expr::BVar(0))),
1066        Box::new(thunk_of(Expr::BVar(1))),
1067    ))
1068}
1069/// `Thunk.itree_vis : {α : Type} → Nat → (Nat → Thunk α) → Thunk α`
1070///
1071/// Interaction tree visible event: models an external call.
1072pub fn axiom_itree_vis_ty() -> Expr {
1073    let cont = Expr::Pi(
1074        BinderInfo::Default,
1075        Name::str("_"),
1076        Box::new(nat_ty()),
1077        Box::new(thunk_of(Expr::BVar(1))),
1078    );
1079    alpha_implicit(Expr::Pi(
1080        BinderInfo::Default,
1081        Name::str("ev"),
1082        Box::new(nat_ty()),
1083        Box::new(Expr::Pi(
1084            BinderInfo::Default,
1085            Name::str("k"),
1086            Box::new(cont),
1087            Box::new(thunk_of(Expr::BVar(2))),
1088        )),
1089    ))
1090}
1091/// `Thunk.lazy_nat_stream : Nat → Thunk Nat`
1092///
1093/// Lazily indexed stream of natural numbers; models ω-sequences.
1094pub fn axiom_lazy_nat_stream_ty() -> Expr {
1095    Expr::Pi(
1096        BinderInfo::Default,
1097        Name::str("n"),
1098        Box::new(nat_ty()),
1099        Box::new(thunk_of(nat_ty())),
1100    )
1101}
1102/// `Thunk.memoTable_lookup : {α : Type} → List (Thunk α) → Nat → Option α`
1103///
1104/// Lazy memoization table lookup.
1105pub fn axiom_memo_table_lookup_ty() -> Expr {
1106    alpha_implicit(Expr::Pi(
1107        BinderInfo::Default,
1108        Name::str("tbl"),
1109        Box::new(list_of(thunk_of(Expr::BVar(0)))),
1110        Box::new(Expr::Pi(
1111            BinderInfo::Default,
1112            Name::str("idx"),
1113            Box::new(nat_ty()),
1114            Box::new(option_of(Expr::BVar(2))),
1115        )),
1116    ))
1117}
1118/// `Thunk.memoTable_insert : {α : Type} → List (Thunk α) → Nat → Thunk α → List (Thunk α)`
1119///
1120/// Insert into a lazy memoization table.
1121pub fn axiom_memo_table_insert_ty() -> Expr {
1122    alpha_implicit(Expr::Pi(
1123        BinderInfo::Default,
1124        Name::str("tbl"),
1125        Box::new(list_of(thunk_of(Expr::BVar(0)))),
1126        Box::new(Expr::Pi(
1127            BinderInfo::Default,
1128            Name::str("idx"),
1129            Box::new(nat_ty()),
1130            Box::new(Expr::Pi(
1131                BinderInfo::Default,
1132                Name::str("v"),
1133                Box::new(thunk_of(Expr::BVar(2))),
1134                Box::new(list_of(thunk_of(Expr::BVar(3)))),
1135            )),
1136        )),
1137    ))
1138}
1139/// `Thunk.lazyTree_branch : {α : Type} → Thunk α → Thunk α → Thunk α`
1140///
1141/// A lazy binary tree node branching left and right.
1142pub fn axiom_lazy_tree_branch_ty() -> Expr {
1143    alpha_implicit(Expr::Pi(
1144        BinderInfo::Default,
1145        Name::str("l"),
1146        Box::new(thunk_of(Expr::BVar(0))),
1147        Box::new(Expr::Pi(
1148            BinderInfo::Default,
1149            Name::str("r"),
1150            Box::new(thunk_of(Expr::BVar(1))),
1151            Box::new(thunk_of(Expr::BVar(2))),
1152        )),
1153    ))
1154}
1155/// `Thunk.lazyTree_depth : {α : Type} → Thunk α → Nat`
1156///
1157/// The depth of a lazy tree (forces all branches).
1158pub fn axiom_lazy_tree_depth_ty() -> Expr {
1159    alpha_implicit(Expr::Pi(
1160        BinderInfo::Default,
1161        Name::str("t"),
1162        Box::new(thunk_of(Expr::BVar(0))),
1163        Box::new(nat_ty()),
1164    ))
1165}
1166/// `Thunk.cofree_out : {α : Type} → Thunk α → α`
1167///
1168/// Cofree comonad out-map: projects the head element.
1169pub fn axiom_cofree_out_ty() -> Expr {
1170    alpha_implicit(Expr::Pi(
1171        BinderInfo::Default,
1172        Name::str("t"),
1173        Box::new(thunk_of(Expr::BVar(0))),
1174        Box::new(Expr::BVar(1)),
1175    ))
1176}
1177/// `Thunk.cofree_tail : {α : Type} → Thunk α → List (Thunk α)`
1178///
1179/// Cofree comonad tail: the sub-computations generated by the head.
1180pub fn axiom_cofree_tail_ty() -> Expr {
1181    alpha_implicit(Expr::Pi(
1182        BinderInfo::Default,
1183        Name::str("t"),
1184        Box::new(thunk_of(Expr::BVar(0))),
1185        Box::new(list_of(thunk_of(Expr::BVar(1)))),
1186    ))
1187}
1188/// `Thunk.whnf_step : {α : Type} → Thunk α → Thunk α`
1189///
1190/// One weak-head normal form reduction step.
1191pub fn axiom_whnf_step_ty() -> Expr {
1192    alpha_implicit(Expr::Pi(
1193        BinderInfo::Default,
1194        Name::str("t"),
1195        Box::new(thunk_of(Expr::BVar(0))),
1196        Box::new(thunk_of(Expr::BVar(1))),
1197    ))
1198}
1199/// `Thunk.ap_naturality : {α β : Type} → ∀ f : α → β, ∀ t : Thunk α, ...`
1200///
1201/// Applicative naturality: map commutes with ap.
1202pub fn axiom_ap_naturality_ty() -> Expr {
1203    thk_ext_ab_prop(thk_ext_prop())
1204}
1205/// `Thunk.bind_assoc : {α β γ : Type} → ∀ t f g, bind (bind t f) g = bind t (fun x => bind (f x) g)`
1206///
1207/// Monad associativity for Thunk.
1208pub fn axiom_bind_assoc_ty() -> Expr {
1209    thk_ext_abc_implicit(thk_ext_prop())
1210}
1211/// `Thunk.pure_map : {α β : Type} → ∀ (f : α → β) (x : α), map f (pure x) = pure (f x)`
1212///
1213/// Applicative homomorphism law.
1214pub fn axiom_pure_map_ty() -> Expr {
1215    thk_ext_ab_prop(thk_ext_prop())
1216}
1217/// `Thunk.lazy_fix : {α : Type} → (Thunk α → α) → Thunk α`
1218///
1219/// Lazy fixed-point combinator: produces a thunk whose value satisfies `f (fix f) = fix f`.
1220pub fn axiom_lazy_fix_ty() -> Expr {
1221    let coalg = Expr::Pi(
1222        BinderInfo::Default,
1223        Name::str("_"),
1224        Box::new(thunk_of(Expr::BVar(1))),
1225        Box::new(Expr::BVar(1)),
1226    );
1227    alpha_implicit(Expr::Pi(
1228        BinderInfo::Default,
1229        Name::str("f"),
1230        Box::new(coalg),
1231        Box::new(thunk_of(Expr::BVar(1))),
1232    ))
1233}
1234/// `Thunk.omega_limit : {α : Type} → (Nat → Thunk α) → Thunk α`
1235///
1236/// ω-limit of a sequence of thunks: models the colimit in the ω-CPO.
1237pub fn axiom_omega_limit_ty() -> Expr {
1238    let seq_fn = Expr::Pi(
1239        BinderInfo::Default,
1240        Name::str("_"),
1241        Box::new(nat_ty()),
1242        Box::new(thunk_of(Expr::BVar(1))),
1243    );
1244    alpha_implicit(Expr::Pi(
1245        BinderInfo::Default,
1246        Name::str("chain"),
1247        Box::new(seq_fn),
1248        Box::new(thunk_of(Expr::BVar(1))),
1249    ))
1250}
1251/// `Thunk.scott_continuity : {α : Type} → Prop`
1252///
1253/// Scott continuity of the forcing operation w.r.t. the ω-CPO structure.
1254pub fn axiom_scott_continuity_ty() -> Expr {
1255    alpha_implicit(thk_ext_prop())
1256}
1257/// `Thunk.kleisli_compose : {α β γ : Type} → (α → Thunk β) → (β → Thunk γ) → α → Thunk γ`
1258///
1259/// Kleisli composition in the Thunk monad.
1260pub fn axiom_kleisli_compose_ty() -> Expr {
1261    let f_ty = Expr::Pi(
1262        BinderInfo::Default,
1263        Name::str("_"),
1264        Box::new(Expr::BVar(2)),
1265        Box::new(thunk_of(Expr::BVar(2))),
1266    );
1267    let g_ty = Expr::Pi(
1268        BinderInfo::Default,
1269        Name::str("_"),
1270        Box::new(Expr::BVar(2)),
1271        Box::new(thunk_of(Expr::BVar(2))),
1272    );
1273    thk_ext_abc_implicit(Expr::Pi(
1274        BinderInfo::Default,
1275        Name::str("f"),
1276        Box::new(f_ty),
1277        Box::new(Expr::Pi(
1278            BinderInfo::Default,
1279            Name::str("g"),
1280            Box::new(g_ty),
1281            Box::new(Expr::Pi(
1282                BinderInfo::Default,
1283                Name::str("x"),
1284                Box::new(Expr::BVar(4)),
1285                Box::new(thunk_of(Expr::BVar(4))),
1286            )),
1287        )),
1288    ))
1289}
1290/// `Thunk.force_deterministic : {α : Type} → ∀ t : Thunk α, ∀ n m : Nat, get t = get t`
1291///
1292/// Determinism: forcing a thunk any number of times yields the same result.
1293pub fn axiom_force_deterministic_ty() -> Expr {
1294    alpha_implicit(Expr::Pi(
1295        BinderInfo::Default,
1296        Name::str("t"),
1297        Box::new(thunk_of(Expr::BVar(0))),
1298        Box::new(Expr::Pi(
1299            BinderInfo::Default,
1300            Name::str("n"),
1301            Box::new(nat_ty()),
1302            Box::new(thk_ext_prop()),
1303        )),
1304    ))
1305}
1306/// Register all extended Thunk comonad axioms into the given environment.
1307pub fn register_thunk_extended(env: &mut Environment) -> Result<(), String> {
1308    let entries: &[(&str, fn() -> Expr)] = &[
1309        ("Thunk.extract", axiom_comonad_extract_ty),
1310        ("Thunk.duplicate", axiom_comonad_duplicate_ty),
1311        ("Thunk.extend", axiom_comonad_extend_ty),
1312        (
1313            "Thunk.extract_duplicate",
1314            axiom_comonad_extract_duplicate_ty,
1315        ),
1316        (
1317            "Thunk.duplicate_extract",
1318            axiom_comonad_duplicate_extract_ty,
1319        ),
1320        (
1321            "Thunk.duplicate_duplicate",
1322            axiom_comonad_duplicate_duplicate_ty,
1323        ),
1324        ("Thunk.cokleisli_compose", axiom_cokleisli_compose_ty),
1325        ("Thunk.map_comp", axiom_functor_map_comp_ty),
1326        ("Thunk.get_mk", axiom_get_mk_ty),
1327        ("Thunk.mk_get", axiom_mk_get_ty),
1328        ("Thunk.sharing", axiom_sharing_ty),
1329        ("Thunk.force_once", axiom_force_once_ty),
1330        ("Thunk.cbn_beta", axiom_cbn_beta_ty),
1331        ("Thunk.presheaf_restriction", axiom_presheaf_restriction_ty),
1332        ("Thunk.productive_step", axiom_productive_step_ty),
1333        ("Thunk.guarded_next", axiom_guarded_next_ty),
1334        ("Thunk.game_question", axiom_game_question_ty),
1335        ("Thunk.game_answer", axiom_game_answer_ty),
1336        ("Thunk.itree_ret", axiom_itree_ret_ty),
1337        ("Thunk.itree_tau", axiom_itree_tau_ty),
1338        ("Thunk.itree_vis", axiom_itree_vis_ty),
1339        ("Thunk.lazy_nat_stream", axiom_lazy_nat_stream_ty),
1340        ("Thunk.memoTable_lookup", axiom_memo_table_lookup_ty),
1341        ("Thunk.memoTable_insert", axiom_memo_table_insert_ty),
1342        ("Thunk.lazyTree_branch", axiom_lazy_tree_branch_ty),
1343        ("Thunk.lazyTree_depth", axiom_lazy_tree_depth_ty),
1344        ("Thunk.cofree_out", axiom_cofree_out_ty),
1345        ("Thunk.cofree_tail", axiom_cofree_tail_ty),
1346        ("Thunk.whnf_step", axiom_whnf_step_ty),
1347        ("Thunk.ap_naturality", axiom_ap_naturality_ty),
1348        ("Thunk.bind_assoc", axiom_bind_assoc_ty),
1349        ("Thunk.pure_map", axiom_pure_map_ty),
1350        ("Thunk.lazy_fix", axiom_lazy_fix_ty),
1351        ("Thunk.omega_limit", axiom_omega_limit_ty),
1352        ("Thunk.scott_continuity", axiom_scott_continuity_ty),
1353        ("Thunk.kleisli_compose", axiom_kleisli_compose_ty),
1354        ("Thunk.force_deterministic", axiom_force_deterministic_ty),
1355    ];
1356    for (name, ty_fn) in entries {
1357        axiom(env, name, ty_fn())?;
1358    }
1359    Ok(())
1360}
1361#[cfg(test)]
1362mod thunk_comonad_tests {
1363    use super::*;
1364    fn base_env_for_ext() -> Environment {
1365        let mut env = Environment::new();
1366        let t1 = type1();
1367        for name in ["Unit", "Bool", "Prod", "List", "Nat", "Option"] {
1368            env.add(Declaration::Axiom {
1369                name: Name::str(name),
1370                univ_params: vec![],
1371                ty: t1.clone(),
1372            })
1373            .unwrap_or(());
1374        }
1375        build_thunk_env(&mut env).expect("build_thunk_env should succeed");
1376        env
1377    }
1378    #[test]
1379    fn test_register_thunk_extended_all_registered() {
1380        let mut env = base_env_for_ext();
1381        let result = register_thunk_extended(&mut env);
1382        assert!(
1383            result.is_ok(),
1384            "register_thunk_extended failed: {:?}",
1385            result
1386        );
1387        assert!(env.get(&Name::str("Thunk.extract")).is_some());
1388        assert!(env.get(&Name::str("Thunk.duplicate")).is_some());
1389        assert!(env.get(&Name::str("Thunk.extend")).is_some());
1390        assert!(env.get(&Name::str("Thunk.cokleisli_compose")).is_some());
1391        assert!(env.get(&Name::str("Thunk.itree_ret")).is_some());
1392        assert!(env.get(&Name::str("Thunk.lazy_fix")).is_some());
1393        assert!(env.get(&Name::str("Thunk.omega_limit")).is_some());
1394    }
1395    #[test]
1396    fn test_comonad_ctx_extract() {
1397        let ctx = ComonadCtx::new("env", 42i32);
1398        assert_eq!(ctx.extract(), 42);
1399    }
1400    #[test]
1401    fn test_comonad_ctx_map() {
1402        let ctx = ComonadCtx::new("env", 10i32);
1403        let ctx2 = ctx.map(|x| x * 2);
1404        assert_eq!(ctx2.extract(), 20);
1405    }
1406    #[test]
1407    fn test_comonad_ctx_extend() {
1408        let ctx = ComonadCtx::new(0u32, 5i32);
1409        let ctx2 = ctx.extend(|c| c.extract() + 1);
1410        assert_eq!(ctx2.extract(), 6);
1411    }
1412    #[test]
1413    fn test_game_arena_ask_answer() {
1414        let mut arena = GameArena::new();
1415        let q = arena.ask();
1416        arena.answer(q, "42");
1417        assert_eq!(arena.get_answer(q), Some("42"));
1418        assert!(arena.is_complete());
1419    }
1420    #[test]
1421    fn test_game_arena_incomplete() {
1422        let mut arena = GameArena::new();
1423        let _q = arena.ask();
1424        assert!(!arena.is_complete());
1425    }
1426    #[test]
1427    fn test_lazy_stream_take() {
1428        let stream: LazyStream<i32> = LazyStream::cons(1, || {
1429            LazyStream::cons(2, || LazyStream::cons(3, || LazyStream::nil()))
1430        });
1431        let elems = stream.take(3);
1432        assert_eq!(elems, vec![1, 2, 3]);
1433    }
1434    #[test]
1435    fn test_lazy_stream_nil() {
1436        let s: LazyStream<i32> = LazyStream::nil();
1437        assert!(s.is_nil());
1438    }
1439    #[test]
1440    fn test_thunk_kleisli_apply() {
1441        let k = ThunkKleisli::new(|x: i32| if x > 0 { Some(x * 2) } else { None });
1442        assert_eq!(k.apply(3), Some(6));
1443        assert_eq!(k.apply(-1), None);
1444    }
1445    #[test]
1446    fn test_thunk_kleisli_compose() {
1447        let k1 = ThunkKleisli::new(|x: i32| Some(x + 1));
1448        let k2 = ThunkKleisli::new(|x: i32| Some(x * 3));
1449        let composed = k1.compose(k2);
1450        assert_eq!(composed.apply(4), Some(15));
1451    }
1452    #[test]
1453    fn test_itree_node_ret() {
1454        let node: ITreeNode<(), i32> = ITreeNode::ret(99);
1455        assert_eq!(node.run(10), Some(99));
1456    }
1457    #[test]
1458    fn test_itree_node_tau() {
1459        let node: ITreeNode<(), i32> = ITreeNode::tau(|| ITreeNode::tau(|| ITreeNode::ret(7)));
1460        assert_eq!(node.run(10), Some(7));
1461    }
1462    #[test]
1463    fn test_itree_node_vis_blocks() {
1464        let node: ITreeNode<&str, i32> = ITreeNode::vis("read", |_| ITreeNode::ret(0));
1465        assert_eq!(node.run(10), None);
1466    }
1467    #[test]
1468    fn test_axiom_comonad_extract_ty_is_pi() {
1469        let ty = axiom_comonad_extract_ty();
1470        assert!(matches!(ty, Expr::Pi(..)));
1471    }
1472    #[test]
1473    fn test_axiom_itree_vis_ty_is_pi() {
1474        let ty = axiom_itree_vis_ty();
1475        assert!(matches!(ty, Expr::Pi(..)));
1476    }
1477}
1478#[cfg(test)]
1479mod tests_thunk_extra {
1480    use super::*;
1481    #[test]
1482    fn test_memo_thunk() {
1483        let mut t = MemoThunk::<i32>::new();
1484        assert!(!t.is_forced());
1485        let v = t.force_with(|| 42);
1486        assert_eq!(v, 42);
1487        assert!(t.is_forced());
1488        let v2 = t.force_with(|| 99);
1489        assert_eq!(v2, 42);
1490        t.reset();
1491        assert!(!t.is_forced());
1492    }
1493    #[test]
1494    fn test_cofree_stream() {
1495        let s = CofreeStream::from_periodic(vec![1, 2, 3]);
1496        assert_eq!(s.extract(), &1);
1497        assert_eq!(*s.nth(0), 1);
1498        assert_eq!(*s.nth(1), 2);
1499        assert_eq!(*s.nth(3), 1);
1500        let t = s.tail();
1501        assert_eq!(t.extract(), &2);
1502        let doubled = s.map(|x| x * 2);
1503        assert_eq!(*doubled.nth(0), 2);
1504        assert_eq!(*doubled.nth(1), 4);
1505    }
1506    #[test]
1507    fn test_compute_node() {
1508        let mut node = ComputeNode::<i32>::new("result");
1509        node.add_dep("input");
1510        assert!(node.is_dirty);
1511        assert!(node.get().is_none());
1512        node.set_value(100);
1513        assert!(!node.is_dirty);
1514        assert_eq!(*node.get().expect("get should succeed"), 100);
1515        node.invalidate();
1516        assert!(node.is_dirty);
1517    }
1518    #[test]
1519    fn test_thunk_app_functor() {
1520        let t = ThunkApp::pure(5);
1521        let t2 = t.map(|x| x * 2);
1522        assert_eq!(t2.val, 10);
1523        let tf = ThunkApp::pure(|x: i32| x + 1);
1524        let t3 = ThunkApp::pure(10);
1525        let result = t3.ap(tf);
1526        assert_eq!(result.val, 11);
1527    }
1528    #[test]
1529    fn test_thunk_state_blackhole() {
1530        let mut s = ThunkState::<i32>::Unevaluated;
1531        assert!(s.enter());
1532        assert!(s.is_blackhole());
1533        assert!(!s.enter());
1534        s.fill(42);
1535        assert!(s.is_value());
1536        assert_eq!(s.get_value(), Some(&42));
1537    }
1538    #[test]
1539    fn test_suspension_monad() {
1540        let s: Suspension<i32> = Suspension::pure(10);
1541        assert!(s.is_done());
1542        let s2 = s.map(|x| x + 5);
1543        assert!(s2.is_done());
1544        let pend: Suspension<i32> = Suspension::suspend("waiting");
1545        let pend2 = pend.map(|x| x * 2);
1546        assert!(!pend2.is_done());
1547        let chained = Suspension::pure(3).and_then(|x| Suspension::pure(x * x));
1548        assert!(chained.is_done());
1549    }
1550}