1use std::collections::{HashMap, HashSet};
29use std::fmt;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Effect {
34 IO,
36 State,
38 NonDet,
40 Exception,
42 Diverge,
44 Alloc,
46 Network,
48 FileSystem,
50 Log,
52 Time,
54 GPU,
56 Concurrent,
58 Env,
60 System,
62}
63
64impl Effect {
65 pub fn name(&self) -> &'static str {
67 match self {
68 Effect::IO => "IO",
69 Effect::State => "State",
70 Effect::NonDet => "NonDet",
71 Effect::Exception => "Exception",
72 Effect::Diverge => "Diverge",
73 Effect::Alloc => "Alloc",
74 Effect::Network => "Network",
75 Effect::FileSystem => "FileSystem",
76 Effect::Log => "Log",
77 Effect::Time => "Time",
78 Effect::GPU => "GPU",
79 Effect::Concurrent => "Concurrent",
80 Effect::Env => "Env",
81 Effect::System => "System",
82 }
83 }
84
85 pub fn description(&self) -> &'static str {
87 match self {
88 Effect::IO => "Input/output operations",
89 Effect::State => "State mutation",
90 Effect::NonDet => "Non-deterministic computation",
91 Effect::Exception => "May raise exceptions",
92 Effect::Diverge => "May not terminate",
93 Effect::Alloc => "Memory allocation",
94 Effect::Network => "Network communication",
95 Effect::FileSystem => "File system access",
96 Effect::Log => "Logging/tracing",
97 Effect::Time => "Time-dependent operations",
98 Effect::GPU => "GPU computation",
99 Effect::Concurrent => "Concurrent/parallel execution",
100 Effect::Env => "Environment variable access",
101 Effect::System => "System calls",
102 }
103 }
104
105 pub fn implies(&self, other: &Effect) -> bool {
109 match (self, other) {
110 (Effect::FileSystem, Effect::IO) => true,
111 (Effect::Network, Effect::IO) => true,
112 (Effect::GPU, Effect::Alloc) => true,
113 _ => self == other,
114 }
115 }
116}
117
118impl fmt::Display for Effect {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "{}", self.name())
121 }
122}
123
124#[derive(Debug, Clone, Default, PartialEq, Eq)]
126pub struct EffectSet {
127 effects: HashSet<Effect>,
129}
130
131impl EffectSet {
132 pub fn new() -> Self {
134 EffectSet {
135 effects: HashSet::new(),
136 }
137 }
138
139 pub fn pure() -> Self {
141 Self::new()
142 }
143
144 pub fn all() -> Self {
146 let mut effects = HashSet::new();
147 effects.insert(Effect::IO);
148 effects.insert(Effect::State);
149 effects.insert(Effect::NonDet);
150 effects.insert(Effect::Exception);
151 effects.insert(Effect::Diverge);
152 effects.insert(Effect::Alloc);
153 effects.insert(Effect::Network);
154 effects.insert(Effect::FileSystem);
155 effects.insert(Effect::Log);
156 effects.insert(Effect::Time);
157 effects.insert(Effect::GPU);
158 effects.insert(Effect::Concurrent);
159 effects.insert(Effect::Env);
160 effects.insert(Effect::System);
161 EffectSet { effects }
162 }
163
164 pub fn singleton(effect: Effect) -> Self {
166 let mut effects = HashSet::new();
167 effects.insert(effect);
168 EffectSet { effects }
169 }
170
171 pub fn with(mut self, effect: Effect) -> Self {
173 self.effects.insert(effect);
174 self
175 }
176
177 pub fn insert(&mut self, effect: Effect) {
179 self.effects.insert(effect);
180 }
181
182 pub fn remove(&mut self, effect: &Effect) {
184 self.effects.remove(effect);
185 }
186
187 pub fn has(&self, effect: Effect) -> bool {
189 self.effects.contains(&effect)
190 }
191
192 pub fn is_pure(&self) -> bool {
194 self.effects.is_empty()
195 }
196
197 pub fn is_total(&self) -> bool {
199 !self.has(Effect::Diverge) && !self.has(Effect::Exception)
200 }
201
202 pub fn is_deterministic(&self) -> bool {
204 !self.has(Effect::NonDet)
205 }
206
207 pub fn union(&self, other: &EffectSet) -> EffectSet {
209 let effects: HashSet<_> = self.effects.union(&other.effects).cloned().collect();
210 EffectSet { effects }
211 }
212
213 pub fn intersection(&self, other: &EffectSet) -> EffectSet {
215 let effects: HashSet<_> = self.effects.intersection(&other.effects).cloned().collect();
216 EffectSet { effects }
217 }
218
219 pub fn difference(&self, other: &EffectSet) -> EffectSet {
221 let effects: HashSet<_> = self.effects.difference(&other.effects).cloned().collect();
222 EffectSet { effects }
223 }
224
225 pub fn is_subset_of(&self, other: &EffectSet) -> bool {
227 self.effects.is_subset(&other.effects)
228 }
229
230 pub fn len(&self) -> usize {
232 self.effects.len()
233 }
234
235 pub fn is_empty(&self) -> bool {
237 self.effects.is_empty()
238 }
239
240 pub fn iter(&self) -> impl Iterator<Item = &Effect> {
242 self.effects.iter()
243 }
244
245 pub fn to_vec(&self) -> Vec<Effect> {
247 self.effects.iter().cloned().collect()
248 }
249
250 pub fn expand_implications(&mut self) {
254 let current: Vec<_> = self.effects.iter().cloned().collect();
255 for effect in current {
256 match effect {
257 Effect::FileSystem | Effect::Network => {
258 self.effects.insert(Effect::IO);
259 }
260 Effect::GPU => {
261 self.effects.insert(Effect::Alloc);
262 }
263 _ => {}
264 }
265 }
266 }
267}
268
269impl fmt::Display for EffectSet {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 if self.is_empty() {
272 write!(f, "Pure")
273 } else {
274 let names: Vec<_> = self.effects.iter().map(|e| e.name()).collect();
275 write!(f, "{{{}}}", names.join(", "))
276 }
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Eq)]
284pub enum EffectRow {
285 Closed(EffectSet),
287 Open { effects: EffectSet, tail: String },
289}
290
291impl EffectRow {
292 pub fn closed(effects: EffectSet) -> Self {
294 EffectRow::Closed(effects)
295 }
296
297 pub fn open(effects: EffectSet, tail: impl Into<String>) -> Self {
299 EffectRow::Open {
300 effects,
301 tail: tail.into(),
302 }
303 }
304
305 pub fn pure() -> Self {
307 EffectRow::Closed(EffectSet::pure())
308 }
309
310 pub fn has(&self, effect: Effect) -> bool {
312 match self {
313 EffectRow::Closed(effects) => effects.has(effect),
314 EffectRow::Open { effects, .. } => effects.has(effect),
315 }
316 }
317
318 pub fn free_variables(&self) -> Vec<String> {
320 match self {
321 EffectRow::Closed(_) => vec![],
322 EffectRow::Open { tail, .. } => vec![tail.clone()],
323 }
324 }
325
326 pub fn substitute(&self, var: &str, row: &EffectRow) -> EffectRow {
328 match self {
329 EffectRow::Closed(effects) => EffectRow::Closed(effects.clone()),
330 EffectRow::Open { effects, tail } => {
331 if tail == var {
332 match row {
333 EffectRow::Closed(other_effects) => {
334 EffectRow::Closed(effects.union(other_effects))
335 }
336 EffectRow::Open {
337 effects: other_effects,
338 tail: other_tail,
339 } => EffectRow::Open {
340 effects: effects.union(other_effects),
341 tail: other_tail.clone(),
342 },
343 }
344 } else {
345 EffectRow::Open {
346 effects: effects.clone(),
347 tail: tail.clone(),
348 }
349 }
350 }
351 }
352 }
353}
354
355impl fmt::Display for EffectRow {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 match self {
358 EffectRow::Closed(effects) => write!(f, "{}", effects),
359 EffectRow::Open { effects, tail } => {
360 if effects.is_empty() {
361 write!(f, "{}", tail)
362 } else {
363 let names: Vec<_> = effects.iter().map(|e| e.name()).collect();
364 write!(f, "{{{} | {}}}", names.join(", "), tail)
365 }
366 }
367 }
368 }
369}
370
371#[derive(Debug, Clone)]
373pub struct EffectHandler {
374 pub name: String,
376 pub handles: EffectSet,
378 pub description: Option<String>,
380}
381
382impl EffectHandler {
383 pub fn new(name: impl Into<String>) -> Self {
385 EffectHandler {
386 name: name.into(),
387 handles: EffectSet::new(),
388 description: None,
389 }
390 }
391
392 pub fn with_effect(mut self, effect: Effect) -> Self {
394 self.handles.insert(effect);
395 self
396 }
397
398 pub fn with_description(mut self, description: impl Into<String>) -> Self {
400 self.description = Some(description.into());
401 self
402 }
403
404 pub fn handles(&self, effect: Effect) -> bool {
406 self.handles.has(effect)
407 }
408
409 pub fn residual(&self, effects: &EffectSet) -> EffectSet {
411 effects.difference(&self.handles)
412 }
413}
414
415#[derive(Debug, Clone, Default)]
417pub struct EffectContext {
418 annotations: HashMap<String, EffectSet>,
420 handlers: Vec<EffectHandler>,
422 variables: HashMap<String, EffectSet>,
424}
425
426impl EffectContext {
427 pub fn new() -> Self {
429 EffectContext {
430 annotations: HashMap::new(),
431 handlers: Vec::new(),
432 variables: HashMap::new(),
433 }
434 }
435
436 pub fn annotate(&mut self, name: impl Into<String>, effects: EffectSet) {
438 self.annotations.insert(name.into(), effects);
439 }
440
441 pub fn get_effects(&self, name: &str) -> Option<&EffectSet> {
443 self.annotations.get(name)
444 }
445
446 pub fn install_handler(&mut self, handler: EffectHandler) {
448 self.handlers.push(handler);
449 }
450
451 pub fn set_variable(&mut self, name: impl Into<String>, effects: EffectSet) {
453 self.variables.insert(name.into(), effects);
454 }
455
456 pub fn get_variable(&self, name: &str) -> Option<&EffectSet> {
458 self.variables.get(name)
459 }
460
461 pub fn compute_residual(&self, effects: &EffectSet) -> EffectSet {
463 let mut residual = effects.clone();
464 for handler in &self.handlers {
465 residual = handler.residual(&residual);
466 }
467 residual
468 }
469
470 pub fn all_handled(&self, effects: &EffectSet) -> bool {
472 self.compute_residual(effects).is_empty()
473 }
474
475 pub fn unhandled(&self, effects: &EffectSet) -> EffectSet {
477 self.compute_residual(effects)
478 }
479}
480
481#[derive(Debug, Clone, Default)]
483pub struct EffectRegistry {
484 functions: HashMap<String, EffectSignature>,
486}
487
488#[derive(Debug, Clone)]
490pub struct EffectSignature {
491 pub name: String,
493 pub effects: EffectRow,
495 pub description: Option<String>,
497}
498
499impl EffectRegistry {
500 pub fn new() -> Self {
502 EffectRegistry {
503 functions: HashMap::new(),
504 }
505 }
506
507 pub fn with_builtins() -> Self {
509 let mut registry = EffectRegistry::new();
510
511 registry.register(EffectSignature {
513 name: "sin".to_string(),
514 effects: EffectRow::pure(),
515 description: Some("Sine function".to_string()),
516 });
517
518 registry.register(EffectSignature {
519 name: "cos".to_string(),
520 effects: EffectRow::pure(),
521 description: Some("Cosine function".to_string()),
522 });
523
524 registry.register(EffectSignature {
525 name: "exp".to_string(),
526 effects: EffectRow::pure(),
527 description: Some("Exponential function".to_string()),
528 });
529
530 registry.register(EffectSignature {
532 name: "print".to_string(),
533 effects: EffectRow::closed(EffectSet::singleton(Effect::IO)),
534 description: Some("Print to stdout".to_string()),
535 });
536
537 registry.register(EffectSignature {
538 name: "read_file".to_string(),
539 effects: EffectRow::closed(
540 EffectSet::new()
541 .with(Effect::IO)
542 .with(Effect::FileSystem)
543 .with(Effect::Exception),
544 ),
545 description: Some("Read file contents".to_string()),
546 });
547
548 registry.register(EffectSignature {
550 name: "random".to_string(),
551 effects: EffectRow::closed(EffectSet::singleton(Effect::NonDet)),
552 description: Some("Generate random number".to_string()),
553 });
554
555 registry.register(EffectSignature {
557 name: "gpu_matmul".to_string(),
558 effects: EffectRow::closed(EffectSet::new().with(Effect::GPU).with(Effect::Alloc)),
559 description: Some("GPU matrix multiplication".to_string()),
560 });
561
562 registry
563 }
564
565 pub fn register(&mut self, signature: EffectSignature) {
567 self.functions.insert(signature.name.clone(), signature);
568 }
569
570 pub fn get(&self, name: &str) -> Option<&EffectSignature> {
572 self.functions.get(name)
573 }
574
575 pub fn contains(&self, name: &str) -> bool {
577 self.functions.contains_key(name)
578 }
579
580 pub fn function_names(&self) -> Vec<&str> {
582 self.functions.keys().map(|s| s.as_str()).collect()
583 }
584
585 pub fn len(&self) -> usize {
587 self.functions.len()
588 }
589
590 pub fn is_empty(&self) -> bool {
592 self.functions.is_empty()
593 }
594
595 pub fn is_pure(&self, name: &str) -> Option<bool> {
597 self.functions.get(name).map(|sig| match &sig.effects {
598 EffectRow::Closed(effects) => effects.is_pure(),
599 EffectRow::Open { effects, .. } => effects.is_pure(),
600 })
601 }
602}
603
604pub fn infer_effects(registry: &EffectRegistry, operations: &[&str]) -> EffectSet {
606 let mut effects = EffectSet::new();
607 for op in operations {
608 if let Some(sig) = registry.get(op) {
609 match &sig.effects {
610 EffectRow::Closed(op_effects) => {
611 effects = effects.union(op_effects);
612 }
613 EffectRow::Open {
614 effects: op_effects,
615 ..
616 } => {
617 effects = effects.union(op_effects);
618 }
619 }
620 }
621 }
622 effects
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628
629 #[test]
630 fn test_effect_set_operations() {
631 let io = EffectSet::singleton(Effect::IO);
632 let state = EffectSet::singleton(Effect::State);
633
634 let combined = io.union(&state);
635 assert!(combined.has(Effect::IO));
636 assert!(combined.has(Effect::State));
637 assert_eq!(combined.len(), 2);
638 }
639
640 #[test]
641 fn test_effect_set_pure() {
642 let pure = EffectSet::pure();
643 assert!(pure.is_pure());
644 assert!(pure.is_total());
645 assert!(pure.is_deterministic());
646 }
647
648 #[test]
649 fn test_effect_set_subset() {
650 let io = EffectSet::singleton(Effect::IO);
651 let combined = EffectSet::new().with(Effect::IO).with(Effect::State);
652
653 assert!(io.is_subset_of(&combined));
654 assert!(!combined.is_subset_of(&io));
655 }
656
657 #[test]
658 fn test_effect_set_difference() {
659 let combined = EffectSet::new().with(Effect::IO).with(Effect::State);
660 let io = EffectSet::singleton(Effect::IO);
661
662 let diff = combined.difference(&io);
663 assert!(!diff.has(Effect::IO));
664 assert!(diff.has(Effect::State));
665 }
666
667 #[test]
668 fn test_effect_row_closed() {
669 let row = EffectRow::closed(EffectSet::singleton(Effect::IO));
670 assert!(row.has(Effect::IO));
671 assert!(!row.has(Effect::State));
672 }
673
674 #[test]
675 fn test_effect_row_open() {
676 let row = EffectRow::open(EffectSet::singleton(Effect::IO), "e");
677 assert!(row.has(Effect::IO));
678 assert_eq!(row.free_variables(), vec!["e".to_string()]);
679 }
680
681 #[test]
682 fn test_effect_row_substitute() {
683 let row = EffectRow::open(EffectSet::singleton(Effect::IO), "e");
684 let tail = EffectRow::closed(EffectSet::singleton(Effect::State));
685
686 let result = row.substitute("e", &tail);
687 match result {
688 EffectRow::Closed(effects) => {
689 assert!(effects.has(Effect::IO));
690 assert!(effects.has(Effect::State));
691 }
692 _ => panic!("Expected closed row"),
693 }
694 }
695
696 #[test]
697 fn test_effect_handler() {
698 let handler = EffectHandler::new("io_handler")
699 .with_effect(Effect::IO)
700 .with_effect(Effect::FileSystem);
701
702 let effects = EffectSet::new()
703 .with(Effect::IO)
704 .with(Effect::State)
705 .with(Effect::FileSystem);
706
707 let residual = handler.residual(&effects);
708 assert!(!residual.has(Effect::IO));
709 assert!(!residual.has(Effect::FileSystem));
710 assert!(residual.has(Effect::State));
711 }
712
713 #[test]
714 fn test_effect_context() {
715 let mut ctx = EffectContext::new();
716
717 ctx.annotate("foo", EffectSet::singleton(Effect::IO));
718 ctx.annotate("bar", EffectSet::pure());
719
720 assert!(ctx.get_effects("foo").unwrap().has(Effect::IO));
721 assert!(ctx.get_effects("bar").unwrap().is_pure());
722 }
723
724 #[test]
725 fn test_effect_context_handlers() {
726 let mut ctx = EffectContext::new();
727
728 ctx.install_handler(EffectHandler::new("io").with_effect(Effect::IO));
729
730 let effects = EffectSet::new().with(Effect::IO).with(Effect::State);
731 let residual = ctx.compute_residual(&effects);
732
733 assert!(!residual.has(Effect::IO));
734 assert!(residual.has(Effect::State));
735 }
736
737 #[test]
738 fn test_effect_registry_builtins() {
739 let registry = EffectRegistry::with_builtins();
740
741 assert!(registry.is_pure("sin").unwrap());
742 assert!(!registry.is_pure("print").unwrap());
743 assert!(!registry.is_pure("random").unwrap());
744 }
745
746 #[test]
747 fn test_infer_effects() {
748 let registry = EffectRegistry::with_builtins();
749
750 let effects = infer_effects(®istry, &["sin", "cos"]);
751 assert!(effects.is_pure());
752
753 let effects = infer_effects(®istry, &["sin", "print"]);
754 assert!(effects.has(Effect::IO));
755 }
756
757 #[test]
758 fn test_effect_implies() {
759 assert!(Effect::FileSystem.implies(&Effect::IO));
760 assert!(Effect::Network.implies(&Effect::IO));
761 assert!(!Effect::IO.implies(&Effect::FileSystem));
762 }
763
764 #[test]
765 fn test_expand_implications() {
766 let mut effects = EffectSet::new().with(Effect::FileSystem);
767 effects.expand_implications();
768
769 assert!(effects.has(Effect::IO));
770 assert!(effects.has(Effect::FileSystem));
771 }
772
773 #[test]
774 fn test_effect_display() {
775 let pure = EffectSet::pure();
776 assert_eq!(format!("{}", pure), "Pure");
777
778 let row = EffectRow::open(EffectSet::singleton(Effect::IO), "e");
779 assert!(format!("{}", row).contains("IO"));
780 assert!(format!("{}", row).contains("e"));
781 }
782
783 #[test]
784 fn test_is_total() {
785 let effects = EffectSet::new().with(Effect::IO).with(Effect::State);
786 assert!(effects.is_total());
787
788 let effects = EffectSet::new().with(Effect::Exception);
789 assert!(!effects.is_total());
790 }
791
792 #[test]
793 fn test_all_effects() {
794 let all = EffectSet::all();
795 assert!(all.has(Effect::IO));
796 assert!(all.has(Effect::State));
797 assert!(all.has(Effect::GPU));
798 assert!(all.has(Effect::System));
799 }
800
801 #[test]
802 fn test_effect_signature() {
803 let sig = EffectSignature {
804 name: "my_func".to_string(),
805 effects: EffectRow::closed(EffectSet::singleton(Effect::IO)),
806 description: Some("My function".to_string()),
807 };
808
809 assert_eq!(sig.name, "my_func");
810 assert!(sig.effects.has(Effect::IO));
811 }
812}