1use csw_core::{CategorySpec, DiagonalSpec, TerminalSpec};
7
8use crate::{
9 EquivalenceRule, ReductionRule, StructuralRules, TermConstructor, TermConstructorKind,
10 TypeConstructor, TypeConstructorKind, TypeSystem, TypingRule,
11};
12
13pub struct Deriver;
18
19impl Deriver {
20 pub fn derive(spec: &CategorySpec) -> TypeSystem {
42 let mut ts = TypeSystem {
43 name: spec.name.clone(),
44 structural: StructuralRules {
45 weakening: matches!(spec.structure.terminal_morphism, TerminalSpec::Universal),
46 contraction: matches!(spec.structure.diagonal, DiagonalSpec::Universal),
47 exchange: spec.structure.symmetry,
48 },
49 ..Default::default()
50 };
51
52 Self::derive_variable(&mut ts);
54
55 for obj in &spec.base_objects {
57 ts.type_constructors.push(TypeConstructor {
58 name: obj.name.clone(),
59 symbol: obj.name.clone(),
60 arity: 0,
61 kind: TypeConstructorKind::Base,
62 });
63 }
64
65 if spec.structure.terminal {
67 Self::derive_terminal(&mut ts);
68 }
69
70 if spec.structure.initial {
71 Self::derive_initial(&mut ts);
72 }
73
74 if spec.structure.products {
75 Self::derive_products(&mut ts, spec);
76 }
77
78 if spec.structure.coproducts {
79 Self::derive_coproducts(&mut ts);
80 }
81
82 if spec.structure.exponentials {
83 Self::derive_exponentials(&mut ts);
84 }
85
86 if spec.structure.tensor.is_some() {
87 Self::derive_tensor(&mut ts, spec);
88 }
89
90 if spec.structure.linear_hom {
91 Self::derive_linear_hom(&mut ts);
92 }
93
94 ts
95 }
96
97 fn derive_variable(ts: &mut TypeSystem) {
98 ts.term_constructors.push(TermConstructor {
99 name: "var".to_string(),
100 symbol: "x".to_string(),
101 kind: TermConstructorKind::Variable,
102 });
103
104 ts.typing_rules.push(TypingRule {
105 name: "var".to_string(),
106 premises: vec![],
107 conclusion: "Γ, x:A ⊢ x : A".to_string(),
108 side_conditions: vec![],
109 });
110 }
111
112 fn derive_terminal(ts: &mut TypeSystem) {
113 ts.type_constructors.push(TypeConstructor {
115 name: "Unit".to_string(),
116 symbol: "1".to_string(),
117 arity: 0,
118 kind: TypeConstructorKind::Unit,
119 });
120
121 ts.term_constructors.push(TermConstructor {
123 name: "unit".to_string(),
124 symbol: "()".to_string(),
125 kind: TermConstructorKind::UnitIntro,
126 });
127
128 ts.typing_rules.push(TypingRule {
130 name: "unit-intro".to_string(),
131 premises: vec![],
132 conclusion: "Γ ⊢ () : 1".to_string(),
133 side_conditions: vec![],
134 });
135
136 ts.equivalence_rules.push(EquivalenceRule {
138 name: "unit-eta".to_string(),
139 lhs: "e".to_string(),
140 rhs: "()".to_string(),
141 condition: Some("e : 1".to_string()),
142 });
143 }
144
145 fn derive_initial(ts: &mut TypeSystem) {
146 ts.type_constructors.push(TypeConstructor {
148 name: "Empty".to_string(),
149 symbol: "0".to_string(),
150 arity: 0,
151 kind: TypeConstructorKind::Empty,
152 });
153
154 ts.term_constructors.push(TermConstructor {
156 name: "absurd".to_string(),
157 symbol: "absurd".to_string(),
158 kind: TermConstructorKind::Absurd,
159 });
160
161 ts.typing_rules.push(TypingRule {
163 name: "absurd-elim".to_string(),
164 premises: vec!["Γ ⊢ e : 0".to_string()],
165 conclusion: "Γ ⊢ absurd e : A".to_string(),
166 side_conditions: vec![],
167 });
168 }
169
170 fn derive_products(ts: &mut TypeSystem, spec: &CategorySpec) {
171 ts.type_constructors.push(TypeConstructor {
173 name: "Product".to_string(),
174 symbol: "×".to_string(),
175 arity: 2,
176 kind: TypeConstructorKind::Product,
177 });
178
179 ts.term_constructors.push(TermConstructor {
181 name: "pair".to_string(),
182 symbol: "(_, _)".to_string(),
183 kind: TermConstructorKind::PairIntro,
184 });
185 ts.term_constructors.push(TermConstructor {
186 name: "fst".to_string(),
187 symbol: "fst".to_string(),
188 kind: TermConstructorKind::PairElimFst,
189 });
190 ts.term_constructors.push(TermConstructor {
191 name: "snd".to_string(),
192 symbol: "snd".to_string(),
193 kind: TermConstructorKind::PairElimSnd,
194 });
195
196 ts.typing_rules.push(TypingRule {
198 name: "pair-intro".to_string(),
199 premises: vec!["Γ ⊢ a : A".to_string(), "Γ ⊢ b : B".to_string()],
200 conclusion: "Γ ⊢ (a, b) : A × B".to_string(),
201 side_conditions: vec![],
202 });
203
204 ts.typing_rules.push(TypingRule {
205 name: "fst-elim".to_string(),
206 premises: vec!["Γ ⊢ p : A × B".to_string()],
207 conclusion: "Γ ⊢ fst p : A".to_string(),
208 side_conditions: vec![],
209 });
210
211 ts.typing_rules.push(TypingRule {
212 name: "snd-elim".to_string(),
213 premises: vec!["Γ ⊢ p : A × B".to_string()],
214 conclusion: "Γ ⊢ snd p : B".to_string(),
215 side_conditions: vec![],
216 });
217
218 ts.reduction_rules.push(ReductionRule {
220 name: "fst-beta".to_string(),
221 lhs: "fst (a, b)".to_string(),
222 rhs: "a".to_string(),
223 });
224 ts.reduction_rules.push(ReductionRule {
225 name: "snd-beta".to_string(),
226 lhs: "snd (a, b)".to_string(),
227 rhs: "b".to_string(),
228 });
229
230 if matches!(spec.structure.diagonal, DiagonalSpec::Universal) {
232 ts.equivalence_rules.push(EquivalenceRule {
233 name: "pair-eta".to_string(),
234 lhs: "p".to_string(),
235 rhs: "(fst p, snd p)".to_string(),
236 condition: Some("p : A × B".to_string()),
237 });
238 }
239 }
240
241 fn derive_coproducts(ts: &mut TypeSystem) {
242 ts.type_constructors.push(TypeConstructor {
244 name: "Coproduct".to_string(),
245 symbol: "+".to_string(),
246 arity: 2,
247 kind: TypeConstructorKind::Coproduct,
248 });
249
250 ts.term_constructors.push(TermConstructor {
252 name: "inl".to_string(),
253 symbol: "inl".to_string(),
254 kind: TermConstructorKind::InjLeft,
255 });
256 ts.term_constructors.push(TermConstructor {
257 name: "inr".to_string(),
258 symbol: "inr".to_string(),
259 kind: TermConstructorKind::InjRight,
260 });
261 ts.term_constructors.push(TermConstructor {
262 name: "case".to_string(),
263 symbol: "case _ of inl x ⇒ _ | inr y ⇒ _".to_string(),
264 kind: TermConstructorKind::Case,
265 });
266
267 ts.typing_rules.push(TypingRule {
269 name: "inl-intro".to_string(),
270 premises: vec!["Γ ⊢ a : A".to_string()],
271 conclusion: "Γ ⊢ inl a : A + B".to_string(),
272 side_conditions: vec![],
273 });
274
275 ts.typing_rules.push(TypingRule {
276 name: "inr-intro".to_string(),
277 premises: vec!["Γ ⊢ b : B".to_string()],
278 conclusion: "Γ ⊢ inr b : A + B".to_string(),
279 side_conditions: vec![],
280 });
281
282 ts.typing_rules.push(TypingRule {
283 name: "case-elim".to_string(),
284 premises: vec![
285 "Γ ⊢ e : A + B".to_string(),
286 "Γ, x:A ⊢ e₁ : C".to_string(),
287 "Γ, y:B ⊢ e₂ : C".to_string(),
288 ],
289 conclusion: "Γ ⊢ case e of inl x ⇒ e₁ | inr y ⇒ e₂ : C".to_string(),
290 side_conditions: vec![],
291 });
292
293 ts.reduction_rules.push(ReductionRule {
295 name: "case-inl-beta".to_string(),
296 lhs: "case (inl a) of inl x ⇒ e₁ | inr y ⇒ e₂".to_string(),
297 rhs: "e₁[a/x]".to_string(),
298 });
299 ts.reduction_rules.push(ReductionRule {
300 name: "case-inr-beta".to_string(),
301 lhs: "case (inr b) of inl x ⇒ e₁ | inr y ⇒ e₂".to_string(),
302 rhs: "e₂[b/y]".to_string(),
303 });
304
305 ts.equivalence_rules.push(EquivalenceRule {
307 name: "sum-eta".to_string(),
308 lhs: "e".to_string(),
309 rhs: "case e of inl x ⇒ inl x | inr y ⇒ inr y".to_string(),
310 condition: Some("e : A + B".to_string()),
311 });
312 }
313
314 fn derive_exponentials(ts: &mut TypeSystem) {
315 ts.type_constructors.push(TypeConstructor {
317 name: "Arrow".to_string(),
318 symbol: "→".to_string(),
319 arity: 2,
320 kind: TypeConstructorKind::Exponential,
321 });
322
323 ts.term_constructors.push(TermConstructor {
325 name: "abs".to_string(),
326 symbol: "λ_._ ".to_string(),
327 kind: TermConstructorKind::Abstraction,
328 });
329 ts.term_constructors.push(TermConstructor {
330 name: "app".to_string(),
331 symbol: "_ _".to_string(),
332 kind: TermConstructorKind::Application,
333 });
334
335 ts.typing_rules.push(TypingRule {
337 name: "abs-intro".to_string(),
338 premises: vec!["Γ, x:A ⊢ e : B".to_string()],
339 conclusion: "Γ ⊢ λx.e : A → B".to_string(),
340 side_conditions: vec![],
341 });
342
343 ts.typing_rules.push(TypingRule {
344 name: "app-elim".to_string(),
345 premises: vec!["Γ ⊢ f : A → B".to_string(), "Γ ⊢ a : A".to_string()],
346 conclusion: "Γ ⊢ f a : B".to_string(),
347 side_conditions: vec![],
348 });
349
350 ts.reduction_rules.push(ReductionRule {
352 name: "beta".to_string(),
353 lhs: "(λx.e) a".to_string(),
354 rhs: "e[a/x]".to_string(),
355 });
356
357 ts.equivalence_rules.push(EquivalenceRule {
359 name: "eta".to_string(),
360 lhs: "f".to_string(),
361 rhs: "λx.f x".to_string(),
362 condition: Some("f : A → B, x fresh".to_string()),
363 });
364 }
365
366 fn derive_tensor(ts: &mut TypeSystem, spec: &CategorySpec) {
367 let tensor_spec = spec.structure.tensor.as_ref().unwrap();
368
369 ts.type_constructors.push(TypeConstructor {
371 name: "Tensor".to_string(),
372 symbol: tensor_spec.symbol.clone(),
373 arity: 2,
374 kind: TypeConstructorKind::Tensor,
375 });
376
377 ts.type_constructors.push(TypeConstructor {
379 name: "TensorUnit".to_string(),
380 symbol: tensor_spec.unit_symbol.clone(),
381 arity: 0,
382 kind: TypeConstructorKind::Unit,
383 });
384
385 ts.term_constructors.push(TermConstructor {
387 name: "tensor-pair".to_string(),
388 symbol: format!("(_ {} _)", tensor_spec.symbol),
389 kind: TermConstructorKind::PairIntro,
390 });
391 ts.term_constructors.push(TermConstructor {
392 name: "let-tensor".to_string(),
393 symbol: "let (x ⊗ y) = _ in _".to_string(),
394 kind: TermConstructorKind::LetPair,
395 });
396
397 ts.typing_rules.push(TypingRule {
399 name: "tensor-intro".to_string(),
400 premises: vec!["Γ₁ ⊢ a : A".to_string(), "Γ₂ ⊢ b : B".to_string()],
401 conclusion: format!("Γ₁, Γ₂ ⊢ (a {} b) : A {} B", tensor_spec.symbol, tensor_spec.symbol),
402 side_conditions: vec!["Γ₁ ∩ Γ₂ = ∅".to_string()],
403 });
404
405 ts.typing_rules.push(TypingRule {
407 name: "tensor-elim".to_string(),
408 premises: vec![
409 format!("Γ₁ ⊢ p : A {} B", tensor_spec.symbol),
410 "Γ₂, x:A, y:B ⊢ e : C".to_string(),
411 ],
412 conclusion: "Γ₁, Γ₂ ⊢ let (x ⊗ y) = p in e : C".to_string(),
413 side_conditions: vec![],
414 });
415
416 ts.reduction_rules.push(ReductionRule {
418 name: "tensor-beta".to_string(),
419 lhs: "let (x ⊗ y) = (a ⊗ b) in e".to_string(),
420 rhs: "e[a/x, b/y]".to_string(),
421 });
422 }
423
424 fn derive_linear_hom(ts: &mut TypeSystem) {
425 ts.type_constructors.push(TypeConstructor {
427 name: "Lollipop".to_string(),
428 symbol: "⊸".to_string(),
429 arity: 2,
430 kind: TypeConstructorKind::LinearHom,
431 });
432
433 ts.term_constructors.push(TermConstructor {
435 name: "linear-abs".to_string(),
436 symbol: "λ°_._ ".to_string(),
437 kind: TermConstructorKind::Abstraction,
438 });
439
440 ts.typing_rules.push(TypingRule {
442 name: "linear-abs-intro".to_string(),
443 premises: vec!["Γ, x:A ⊢ e : B".to_string()],
444 conclusion: "Γ ⊢ λx.e : A ⊸ B".to_string(),
445 side_conditions: vec!["x appears exactly once in e".to_string()],
446 });
447
448 ts.typing_rules.push(TypingRule {
449 name: "linear-app-elim".to_string(),
450 premises: vec!["Γ₁ ⊢ f : A ⊸ B".to_string(), "Γ₂ ⊢ a : A".to_string()],
451 conclusion: "Γ₁, Γ₂ ⊢ f a : B".to_string(),
452 side_conditions: vec!["Γ₁ ∩ Γ₂ = ∅".to_string()],
453 });
454
455 ts.reduction_rules.push(ReductionRule {
457 name: "linear-beta".to_string(),
458 lhs: "(λx.e) a".to_string(),
459 rhs: "e[a/x]".to_string(),
460 });
461
462 ts.equivalence_rules.push(EquivalenceRule {
464 name: "linear-eta".to_string(),
465 lhs: "f".to_string(),
466 rhs: "λx.f x".to_string(),
467 condition: Some("f : A ⊸ B, x fresh".to_string()),
468 });
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475 use csw_core::CategoryBuilder;
476
477 #[test]
478 fn test_derive_stlc() {
479 let ccc = CategoryBuilder::new("STLC")
480 .with_base("Int")
481 .with_terminal()
482 .with_products()
483 .with_exponentials()
484 .cartesian()
485 .build()
486 .unwrap();
487
488 let ts = Deriver::derive(&ccc);
489
490 assert_eq!(ts.name, "STLC");
491 assert!(ts.structural.weakening);
492 assert!(ts.structural.contraction);
493 assert!(ts.structural.exchange);
494
495 let type_names: Vec<_> = ts.type_constructors.iter().map(|t| &t.name).collect();
497 assert!(type_names.contains(&&"Int".to_string()));
498 assert!(type_names.contains(&&"Unit".to_string()));
499 assert!(type_names.contains(&&"Product".to_string()));
500 assert!(type_names.contains(&&"Arrow".to_string()));
501 }
502
503 #[test]
504 fn test_derive_linear() {
505 let smcc = CategoryBuilder::new("Linear")
506 .with_base("Int")
507 .with_tensor()
508 .with_linear_hom()
509 .with_symmetry()
510 .linear()
511 .build()
512 .unwrap();
513
514 let ts = Deriver::derive(&smcc);
515
516 assert_eq!(ts.name, "Linear");
517 assert!(!ts.structural.weakening);
518 assert!(!ts.structural.contraction);
519 assert!(ts.structural.exchange);
520
521 let type_names: Vec<_> = ts.type_constructors.iter().map(|t| &t.name).collect();
523 assert!(type_names.contains(&&"Tensor".to_string()));
524 assert!(type_names.contains(&&"Lollipop".to_string()));
525 }
526
527 #[test]
528 fn test_print_type_system() {
529 let ccc = CategoryBuilder::new("STLC")
530 .with_terminal()
531 .with_products()
532 .with_exponentials()
533 .cartesian()
534 .build()
535 .unwrap();
536
537 let ts = Deriver::derive(&ccc);
538 let output = ts.to_string();
539
540 assert!(output.contains("STLC Type System"));
541 assert!(output.contains("Weakening: ✓"));
542 }
543}