From 167da369af7abf6e98d5f09899f5c6fe6fdcef19 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Tue, 3 Oct 2023 01:39:50 +0200 Subject: [PATCH 01/10] improve README --- README.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index eea0d5f..46fd427 100644 --- a/README.md +++ b/README.md @@ -5,35 +5,30 @@ Rust Implementation of Ladder-Types (parsing, unification, rewriting, etc) ## Ladder Types -*Ladder Types* provide a way to distinguish between multiple *concrete -representations* of the same *abstract / conceptual type*. +In order to implement complex datastructures and algorithms, usually +many layers of abstraction are built ontop of each other, and +consequently higher-level data types are encoded into lower-level data +types, forming a chain of embeddings from concept to `rock bottom' of +byte streams. While a high-level type makes claims about an objects +semantics, it is ambiguous in regard to its concrete syntactical +representation or memory layout. However these concrete +representational forms must be compatible for type-safe compositions. -The intuition is to capture a 'represented-as' relation in the -structure of a type term. Formally, we introduce a new type -constructor, called the *ladder type*, written as `T1 ~ T2`, where -`T1` and `T2` are types. The term `T1 ~ T2` can be read by `T1 is -represented as T2`. +For example in the unix shell, many different tools & utilities exist +concurrently and depending on the application domain, each will +potentially make use of different representational forms. Abstract +concepts like 'natural number' could exist in many representational +forms, e.g. with variation over radices, endianness, digit encoding +etc. - - -This can be particularly useful in contexts where no standard -representational forms exist, e.g. in the unix shell where many -different tools & utilities come into contact, each making use of -potentially different representational forms, depending on the -application domain. - -Trying to introduce a global type system at IPC level would result in -naming conflicts: Abstract concepts like 'natural number' do not have -any standard representational form, as it is the case with other more -contained languages. With a simple type name like this it is not clear -which of the many equivalent representations is required, e.g. for an -*integer number* variations over radices, endianness, digit encoding -etc. exist. - -Usually, many layers of abstraction are built ontop of each other, -where higher-level data types are encoded into lower-level data types, -forming a chain of embeddings from concept to `rock bottom' of byte -streams. +Intuitively, *ladder types* provide a way to distinguish between +multiple *concrete representations* of the same *abstract / conceptual +type*, by capturing the *represented-as* of layered data formats in +the structure of type-terms. Formally, we introduce a new type +constructor, called the *ladder type*, written `T1 ~ T2`, where `T1` +and `T2` are types. The type-term `T1 ~ T2` then expresses the +abstract type of `T1` being represented in terms of the concrete type +`T2`, which can be read by "`T1` represented as `T2`". #### Example @@ -66,15 +61,18 @@ this: ```rust use laddertypes::*; -let mut dict = TypeDict::new(); +fn main() { + let mut dict = TypeDict::new(); -let t1 = dict.parse("<A B~X C>").expect("couldnt parse typeterm"); -let t2 = dict.parse("<<A B~X> C>").expect("couldnt parse typeterm"); + let t1 = dict.parse("<A B~X C>").expect("couldnt parse typeterm"); + let t2 = dict.parse("<<A B~X> C>").expect("couldnt parse typeterm"); -assert_eq!( t1.clone().curry(), t2 ); -assert_eq!( t1, t2.clone().decurry() ); + assert_eq!( t1.clone().curry(), t2 ); + assert_eq!( t1, t2.clone().decurry() ); +} ``` -### License + +## License [GPLv3](COPYING) From 29d1acd6814a2bf1c43448648e27d29b04430b52 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Tue, 3 Oct 2023 03:30:38 +0200 Subject: [PATCH 02/10] implement unparse() --- src/lib.rs | 1 + src/unparser.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/unparser.rs diff --git a/src/lib.rs b/src/lib.rs index 3f725bc..517b36d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod dict; pub mod term; pub mod lexer; pub mod parser; +pub mod unparser; pub mod curry; pub mod lnf; pub mod subtype; diff --git a/src/unparser.rs b/src/unparser.rs new file mode 100644 index 0000000..ccf754d --- /dev/null +++ b/src/unparser.rs @@ -0,0 +1,47 @@ +use crate::{dict::*, term::*}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +impl TypeDict { + pub fn unparse(&self, t: &TypeTerm) -> String { + match t { + TypeTerm::TypeID(id) => self.get_typename(id).unwrap(), + TypeTerm::Num(n) => format!("{}", n), + TypeTerm::Char(c) => match c { + '\0' => "'\\0'".into(), + '\n' => "'\\n'".into(), + '\t' => "'\\t'".into(), + '\'' => "'\\''".into(), + c => format!("'{}'", c) + }, + TypeTerm::Ladder(rungs) => { + let mut s = String::new(); + let mut first = true; + for r in rungs.iter() { + if !first { + s.push('~'); + } + first = false; + s.push_str(&mut self.unparse(r)); + } + s + } + TypeTerm::App(args) => { + let mut s = String::new(); + s.push('<'); + let mut first = true; + for r in args.iter() { + if !first { + s.push(' '); + } + first = false; + s.push_str(&mut self.unparse(r)); + } + s.push('>'); + s + } + } + } +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ From 45f49378fa12cddd0d2dac31b671b9b075ffe4d0 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Tue, 3 Oct 2023 03:34:55 +0200 Subject: [PATCH 03/10] is_syntactic_subtype(): remove option in Err variant --- src/subtype.rs | 8 ++++---- src/test/subtype.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/subtype.rs b/src/subtype.rs index 8f398b1..8c46752 100644 --- a/src/subtype.rs +++ b/src/subtype.rs @@ -19,20 +19,20 @@ impl TypeTerm { None } - pub fn is_syntactic_subtype_of(&self, expected_type: &TypeTerm) -> Result<usize, Option<(usize, usize)>> { + pub fn is_syntactic_subtype_of(&self, expected_type: &TypeTerm) -> Result<usize, (usize, usize)> { if let Some((first_match, provided_type)) = self.is_semantic_subtype_of( expected_type ) { let provided_lnf = provided_type.get_lnf_vec(); let expected_lnf = expected_type.clone().get_lnf_vec(); for i in 0 .. usize::min( provided_lnf.len(), expected_lnf.len() ) { if provided_lnf[i] != expected_lnf[i] { - return Err(Some((first_match, first_match+i))) + return Err((first_match, first_match+i)) } } Ok(first_match) } else { - Err(None) + Err((0,0)) } } @@ -43,7 +43,7 @@ impl TypeTerm { t.is_semantic_subtype_of(self) } - pub fn is_syntactic_supertype_of(&self, t: &TypeTerm) -> Result<usize, Option<(usize, usize)>> { + pub fn is_syntactic_supertype_of(&self, t: &TypeTerm) -> Result<usize, (usize, usize)> { t.is_syntactic_subtype_of(self) } } diff --git a/src/test/subtype.rs b/src/test/subtype.rs index 54ad397..08cc5c7 100644 --- a/src/test/subtype.rs +++ b/src/test/subtype.rs @@ -62,7 +62,7 @@ fn test_syntactic_subtype() { .is_syntactic_subtype_of( &dict.parse("C~G").expect("parse errror") ), - Err(Some((2,3))) + Err((2,3)) ); assert_eq!( @@ -70,7 +70,7 @@ fn test_syntactic_subtype() { .is_syntactic_subtype_of( &dict.parse("G~F~K").expect("parse errror") ), - Err(None) + Err((0,0)) ); assert_eq!( From f45593cfd564d3a702030c56cbca91698a90950f Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Tue, 3 Oct 2023 03:35:29 +0200 Subject: [PATCH 04/10] lnf: remove unnecessary mut --- src/lnf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lnf.rs b/src/lnf.rs index 9e38aea..b284df5 100644 --- a/src/lnf.rs +++ b/src/lnf.rs @@ -45,7 +45,7 @@ impl TypeTerm { } TypeTerm::App(args) => { - let mut args_iter = args.into_iter(); + let args_iter = args.into_iter(); new_ladder.push( TypeTerm::App(vec![]) ); From aacafb318a3935f55e14d56b1663a3052f18d2c8 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Tue, 3 Oct 2023 04:34:47 +0200 Subject: [PATCH 05/10] README: minor improvements --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 46fd427..d4dc395 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,21 @@ Rust Implementation of Ladder-Types (parsing, unification, rewriting, etc) ## Ladder Types In order to implement complex datastructures and algorithms, usually -many layers of abstraction are built ontop of each other, and -consequently higher-level data types are encoded into lower-level data +many layers of abstraction are built ontop of each other. +Consequently higher-level data types are encoded into lower-level data types, forming a chain of embeddings from concept to `rock bottom' of -byte streams. While a high-level type makes claims about an objects -semantics, it is ambiguous in regard to its concrete syntactical -representation or memory layout. However these concrete -representational forms must be compatible for type-safe compositions. +byte streams. While a high-level type makes claims about the +semantics of objects of that type, high-level types are ambiguous in +regard to their concrete syntactical representation or memory +layout. However for compositions to be type-safe, compatibility of +concrete represenations must be ensured. -For example in the unix shell, many different tools & utilities exist -concurrently and depending on the application domain, each will -potentially make use of different representational forms. Abstract -concepts like 'natural number' could exist in many representational -forms, e.g. with variation over radices, endianness, digit encoding -etc. +For example in the unix shell, many different tools & utilities +coexist. Depending on the application domain, each of them will +potentially make use of different representational forms for the same +abstract concepts. E.g. for the concept 'natural number', many +representations do exist, e.g. with variation over radices, +endianness, digit encoding etc. Intuitively, *ladder types* provide a way to distinguish between multiple *concrete representations* of the same *abstract / conceptual @@ -37,7 +38,7 @@ each represented as unix-timestamp written as decimal number in big-endian, encoded as UTF-8 string. ``` -<Seq Timepoint +<Seq TimePoint ~<TimeSince UnixEpoch> ~<Duration Seconds> ~ℕ From 74177d1d30b99e2b436105a46e03008f8cecdf1b Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Mon, 30 Oct 2023 17:22:00 +0100 Subject: [PATCH 06/10] substitutions --- src/term.rs | 30 ++++++++++++++++++++++++++++++ src/test/mod.rs | 1 + src/test/substitution.rs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/test/substitution.rs diff --git a/src/term.rs b/src/term.rs index 25fee14..f5e8f5f 100644 --- a/src/term.rs +++ b/src/term.rs @@ -78,6 +78,36 @@ impl TypeTerm { pub fn char_arg(&mut self, c: char) -> &mut Self { self.arg(TypeTerm::Char(c)) } + + /// recursively apply substitution to all subterms, + /// which will replace all occurences of variables which map + /// some type-term in `subst` + pub fn apply_substitution( + &mut self, + subst: &impl Fn(&TypeID) -> Option<TypeTerm> + ) -> &mut Self { + match self { + TypeTerm::TypeID(typid) => { + if let Some(t) = subst(typid) { + *self = t; + } + } + + TypeTerm::Ladder(rungs) => { + for r in rungs.iter_mut() { + r.apply_substitution(subst); + } + } + TypeTerm::App(args) => { + for r in args.iter_mut() { + r.apply_substitution(subst); + } + } + _ => {} + } + + self + } } //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ diff --git a/src/test/mod.rs b/src/test/mod.rs index 9d13c07..4cde4e3 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -4,4 +4,5 @@ pub mod parser; pub mod curry; pub mod lnf; pub mod subtype; +pub mod substitution; diff --git a/src/test/substitution.rs b/src/test/substitution.rs new file mode 100644 index 0000000..7959b08 --- /dev/null +++ b/src/test/substitution.rs @@ -0,0 +1,32 @@ + +use { + crate::{dict::*, term::*}, + std::iter::FromIterator +}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +#[test] +fn test_subst() { + let mut dict = TypeDict::new(); + + let mut σ = std::collections::HashMap::new(); + + // T --> ℕ + σ.insert + (dict.add_varname(String::from("T")), + dict.parse("ℕ").unwrap()); + + // U --> <Seq Char> + σ.insert + (dict.add_varname(String::from("U")), + dict.parse("<Seq Char>").unwrap()); + + + assert_eq!( + dict.parse("<Seq T~U>").unwrap() + .apply_substitution(&|typid|{ σ.get(typid).cloned() }).clone(), + dict.parse("<Seq ℕ~<Seq Char>>").unwrap() + ); +} + From 5919b7df1fbda1ab8d50c10934183bd0884eff17 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Tue, 31 Oct 2023 16:26:54 +0100 Subject: [PATCH 07/10] wip unification --- src/lib.rs | 2 + src/test/mod.rs | 1 + src/test/unification.rs | 79 +++++++++++++++++++++++++++++ src/unification.rs | 107 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 src/test/unification.rs create mode 100644 src/unification.rs diff --git a/src/lib.rs b/src/lib.rs index 517b36d..1a270cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub mod unparser; pub mod curry; pub mod lnf; pub mod subtype; +pub mod unification; #[cfg(test)] mod test; @@ -15,5 +16,6 @@ mod test; pub use { dict::*, term::*, + unification::* }; diff --git a/src/test/mod.rs b/src/test/mod.rs index 4cde4e3..d116412 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -5,4 +5,5 @@ pub mod curry; pub mod lnf; pub mod subtype; pub mod substitution; +pub mod unification; diff --git a/src/test/unification.rs b/src/test/unification.rs new file mode 100644 index 0000000..40b7d68 --- /dev/null +++ b/src/test/unification.rs @@ -0,0 +1,79 @@ + +use { + crate::{dict::*, term::*, unification::*}, + std::iter::FromIterator +}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +fn test_unify(ts1: &str, ts2: &str, expect_unificator: bool) { + let mut dict = TypeDict::new(); + dict.add_varname(String::from("T")); + dict.add_varname(String::from("U")); + dict.add_varname(String::from("V")); + dict.add_varname(String::from("W")); + + let mut t1 = dict.parse(ts1).unwrap(); + let mut t2 = dict.parse(ts2).unwrap(); + let σ = crate::unify( &t1, &t2 ); + + if expect_unificator { + assert!(σ.is_ok()); + + let σ = σ.unwrap(); + + assert_eq!( + t1.apply_substitution(&|v| σ.get(v).cloned()), + t2.apply_substitution(&|v| σ.get(v).cloned()) + ); + } else { + assert!(! σ.is_ok()); + } +} + +#[test] +fn test_unification_error() { + let mut dict = TypeDict::new(); + dict.add_varname(String::from("T")); + + assert_eq!( + crate::unify( + &dict.parse("<A T>").unwrap(), + &dict.parse("<B T>").unwrap() + ), + + Err(UnificationError { + addr: vec![0], + t1: dict.parse("A").unwrap(), + t2: dict.parse("B").unwrap() + }) + ); + + assert_eq!( + crate::unify( + &dict.parse("<V <U A> T>").unwrap(), + &dict.parse("<V <U B> T>").unwrap() + ), + + Err(UnificationError { + addr: vec![1, 1], + t1: dict.parse("A").unwrap(), + t2: dict.parse("B").unwrap() + }) + ); +} + +#[test] +fn test_unification() { + test_unify("A", "A", true); + test_unify("A", "B", false); + test_unify("<Seq T>", "<Seq Ascii~Char>", true); + test_unify("<Seq T>", "<U Char>", true); + + test_unify( + "<Seq Path~<Seq Char>>~<SepSeq Char '\n'>~<Seq Char>", + "<Seq T~<Seq Char>>~<SepSeq Char '\n'>~<Seq Char>", + true + ); +} + diff --git a/src/unification.rs b/src/unification.rs new file mode 100644 index 0000000..6d7598a --- /dev/null +++ b/src/unification.rs @@ -0,0 +1,107 @@ +use { + std::collections::HashMap, + crate::{term::*, dict::*} +}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct UnificationError { + pub addr: Vec<usize>, + pub t1: TypeTerm, + pub t2: TypeTerm +} + +impl UnificationError { + pub fn new(t1: &TypeTerm, t2: &TypeTerm) -> Self { + UnificationError { + addr: vec![], + t1: t1.clone(), + t2: t2.clone() + } + } +} +/* +struct UnificationProblem { + eqs: Vec<(TypeTerm, TypeTerm)>, + σ: HashMap<TypeID, TypeTerm> +} + +impl UnificationProblem { + pub fn new() -> Self { + UnificationProblem { + eqs: Vec::new(), + σ: HashMap::new() + } + } + + pub fn eval_equation(&mut self, lhs: &TypeTerm, rhs: &TypeTerm) -> Option<UnificationError> { + match (lhs, rhs) { + + } + } + + pub fn solve(self) -> Result<HashMap<TypeID, TypeTerm>, UnificationError> { + + } +} +*/ +pub fn unify( + t1: &TypeTerm, + t2: &TypeTerm +) -> Result<HashMap<TypeID, TypeTerm>, UnificationError> { + let mut σ = HashMap::new(); + + match (t1, t2) { + (TypeTerm::TypeID(TypeID::Var(varid)), t) | + (t, TypeTerm::TypeID(TypeID::Var(varid))) => { + σ.insert(TypeID::Var(*varid), t.clone()); + Ok(σ) + } + + (TypeTerm::TypeID(a1), TypeTerm::TypeID(a2)) => { + if a1 == a2 { Ok(σ) } else { Err(UnificationError::new(&t1, &t2)) } + } + (TypeTerm::Num(n1), TypeTerm::Num(n2)) => { + if n1 == n2 { Ok(σ) } else { Err(UnificationError::new(&t1, &t2)) } + } + (TypeTerm::Char(c1), TypeTerm::Char(c2)) => { + if c1 == c2 { Ok(σ) } else { Err(UnificationError::new(&t1, &t2)) } + } + + (TypeTerm::Ladder(a1), TypeTerm::Ladder(a2)) | + (TypeTerm::App(a1), TypeTerm::App(a2)) => { + if a1.len() == a2.len() { + for (i, (x, y)) in a1.iter().cloned().zip(a2.iter().cloned()).enumerate() { + let (mut x, mut y) = (x.clone(), y.clone()); + x.apply_substitution(&|v| σ.get(v).cloned()); + y.apply_substitution(&|v| σ.get(v).cloned()); + + match unify(&x, &y) { + Ok(τ) => { + for (v,t) in τ { + σ.insert(v,t); + } + } + Err(mut err) => { + err.addr.insert(0, i); + return Err(err); + } + } + } + Ok(σ) + } else { + Err(UnificationError::new(&t1, &t2)) + } + } + + (TypeTerm::Ladder(l1), TypeTerm::Ladder(l2)) => { + Err(UnificationError::new(&t1, &t2)) + } + + _ => Err(UnificationError::new(t1, t2)) + } +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + From bd21a602f3778499a6264c8d6c6aa2060bfe47b3 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Sat, 11 Nov 2023 16:26:30 +0100 Subject: [PATCH 08/10] unification --- src/test/unification.rs | 39 ++++++++++++ src/unification.rs | 135 +++++++++++++++++++--------------------- 2 files changed, 103 insertions(+), 71 deletions(-) diff --git a/src/test/unification.rs b/src/test/unification.rs index 40b7d68..34d355d 100644 --- a/src/test/unification.rs +++ b/src/test/unification.rs @@ -75,5 +75,44 @@ fn test_unification() { "<Seq T~<Seq Char>>~<SepSeq Char '\n'>~<Seq Char>", true ); + + let mut dict = TypeDict::new(); + + dict.add_varname(String::from("T")); + dict.add_varname(String::from("U")); + dict.add_varname(String::from("V")); + dict.add_varname(String::from("W")); + + assert_eq!( + UnificationProblem::new(vec![ + (dict.parse("U").unwrap(), dict.parse("<Seq Char>").unwrap()), + (dict.parse("T").unwrap(), dict.parse("<Seq U>").unwrap()), + ]).solve(), + Ok( + vec![ + // T + (TypeID::Var(0), dict.parse("<Seq <Seq Char>>").unwrap()), + + // U + (TypeID::Var(1), dict.parse("<Seq Char>").unwrap()) + ].into_iter().collect() + ) + ); + + assert_eq!( + UnificationProblem::new(vec![ + (dict.parse("<Seq T>").unwrap(), dict.parse("<Seq W~<Seq Char>>").unwrap()), + (dict.parse("<Seq ℕ>").unwrap(), dict.parse("<Seq W>").unwrap()), + ]).solve(), + Ok( + vec![ + // W + (TypeID::Var(3), dict.parse("ℕ").unwrap()), + + // T + (TypeID::Var(0), dict.parse("ℕ~<Seq Char>").unwrap()) + ].into_iter().collect() + ) + ); } diff --git a/src/unification.rs b/src/unification.rs index 6d7598a..abbc1fe 100644 --- a/src/unification.rs +++ b/src/unification.rs @@ -12,95 +12,88 @@ pub struct UnificationError { pub t2: TypeTerm } -impl UnificationError { - pub fn new(t1: &TypeTerm, t2: &TypeTerm) -> Self { - UnificationError { - addr: vec![], - t1: t1.clone(), - t2: t2.clone() - } - } -} -/* -struct UnificationProblem { - eqs: Vec<(TypeTerm, TypeTerm)>, +pub struct UnificationProblem { + eqs: Vec<(TypeTerm, TypeTerm, Vec<usize>)>, σ: HashMap<TypeID, TypeTerm> } impl UnificationProblem { - pub fn new() -> Self { + pub fn new(eqs: Vec<(TypeTerm, TypeTerm)>) -> Self { UnificationProblem { - eqs: Vec::new(), + eqs: eqs.iter().map(|(lhs,rhs)| (lhs.clone(),rhs.clone(),vec![])).collect(), σ: HashMap::new() } } - pub fn eval_equation(&mut self, lhs: &TypeTerm, rhs: &TypeTerm) -> Option<UnificationError> { - match (lhs, rhs) { - + pub fn eval_equation(&mut self, lhs: TypeTerm, rhs: TypeTerm, addr: Vec<usize>) -> Result<(), UnificationError> { + match (lhs.clone(), rhs.clone()) { + (TypeTerm::TypeID(TypeID::Var(varid)), t) | + (t, TypeTerm::TypeID(TypeID::Var(varid))) => { + self.σ.insert(TypeID::Var(varid), t.clone()); + + // update all values in substitution + let mut new_σ = HashMap::new(); + for (v, tt) in self.σ.iter() { + let mut tt = tt.clone(); + tt.apply_substitution(&|v| self.σ.get(v).cloned()); + new_σ.insert(v.clone(), tt); + } + self.σ = new_σ; + + Ok(()) + } + + (TypeTerm::TypeID(a1), TypeTerm::TypeID(a2)) => { + if a1 == a2 { Ok(()) } else { Err(UnificationError{ addr, t1: lhs, t2: rhs}) } + } + (TypeTerm::Num(n1), TypeTerm::Num(n2)) => { + if n1 == n2 { Ok(()) } else { Err(UnificationError{ addr, t1: lhs, t2: rhs}) } + } + (TypeTerm::Char(c1), TypeTerm::Char(c2)) => { + if c1 == c2 { Ok(()) } else { Err(UnificationError{ addr, t1: lhs, t2: rhs}) } + } + + (TypeTerm::Ladder(a1), TypeTerm::Ladder(a2)) | + (TypeTerm::App(a1), TypeTerm::App(a2)) => { + if a1.len() == a2.len() { + for (i, (x, y)) in a1.iter().cloned().zip(a2.iter().cloned()).enumerate() { + let mut new_addr = addr.clone(); + new_addr.push(i); + self.eqs.push((x, y, new_addr)); + } + Ok(()) + } else { + Err(UnificationError{ addr, t1: lhs, t2: rhs }) + } + } + + (TypeTerm::Ladder(l1), TypeTerm::Ladder(l2)) => { + Err(UnificationError{ addr, t1: lhs, t2: rhs }) + } + + _ => Err(UnificationError{ addr, t1: lhs, t2: rhs}) } } - pub fn solve(self) -> Result<HashMap<TypeID, TypeTerm>, UnificationError> { - + pub fn solve(mut self) -> Result<HashMap<TypeID, TypeTerm>, UnificationError> { + while self.eqs.len() > 0 { + while let Some( (mut lhs,mut rhs,addr) ) = self.eqs.pop() { + lhs.apply_substitution(&|v| self.σ.get(v).cloned()); + rhs.apply_substitution(&|v| self.σ.get(v).cloned()); + self.eval_equation(lhs, rhs, addr)?; + } + } + + Ok(self.σ) } } -*/ + pub fn unify( t1: &TypeTerm, t2: &TypeTerm ) -> Result<HashMap<TypeID, TypeTerm>, UnificationError> { - let mut σ = HashMap::new(); - - match (t1, t2) { - (TypeTerm::TypeID(TypeID::Var(varid)), t) | - (t, TypeTerm::TypeID(TypeID::Var(varid))) => { - σ.insert(TypeID::Var(*varid), t.clone()); - Ok(σ) - } - - (TypeTerm::TypeID(a1), TypeTerm::TypeID(a2)) => { - if a1 == a2 { Ok(σ) } else { Err(UnificationError::new(&t1, &t2)) } - } - (TypeTerm::Num(n1), TypeTerm::Num(n2)) => { - if n1 == n2 { Ok(σ) } else { Err(UnificationError::new(&t1, &t2)) } - } - (TypeTerm::Char(c1), TypeTerm::Char(c2)) => { - if c1 == c2 { Ok(σ) } else { Err(UnificationError::new(&t1, &t2)) } - } - - (TypeTerm::Ladder(a1), TypeTerm::Ladder(a2)) | - (TypeTerm::App(a1), TypeTerm::App(a2)) => { - if a1.len() == a2.len() { - for (i, (x, y)) in a1.iter().cloned().zip(a2.iter().cloned()).enumerate() { - let (mut x, mut y) = (x.clone(), y.clone()); - x.apply_substitution(&|v| σ.get(v).cloned()); - y.apply_substitution(&|v| σ.get(v).cloned()); - - match unify(&x, &y) { - Ok(τ) => { - for (v,t) in τ { - σ.insert(v,t); - } - } - Err(mut err) => { - err.addr.insert(0, i); - return Err(err); - } - } - } - Ok(σ) - } else { - Err(UnificationError::new(&t1, &t2)) - } - } - - (TypeTerm::Ladder(l1), TypeTerm::Ladder(l2)) => { - Err(UnificationError::new(&t1, &t2)) - } - - _ => Err(UnificationError::new(t1, t2)) - } + let mut unification = UnificationProblem::new(vec![ (t1.clone(), t2.clone()) ]); + unification.solve() } //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ From 0fb3f6e21216177ea2038e613d2fd7c8f3c198b3 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Sat, 11 Nov 2023 16:26:58 +0100 Subject: [PATCH 09/10] parser test: also test variable-ids --- src/test/parser.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/parser.rs b/src/test/parser.rs index fe75075..1166229 100644 --- a/src/test/parser.rs +++ b/src/test/parser.rs @@ -7,9 +7,18 @@ use { #[test] fn test_parser_id() { + let mut dict = TypeDict::new(); + + dict.add_varname("T".into()); + + assert_eq!( + Ok(TypeTerm::TypeID(TypeID::Var(0))), + dict.parse("T") + ); + assert_eq!( Ok(TypeTerm::TypeID(TypeID::Fun(0))), - TypeDict::new().parse("A") + dict.parse("A") ); } From d7502e6af836210e64a2ec225ee7c4c807099297 Mon Sep 17 00:00:00 2001 From: Michael Sippel <micha@fragmental.art> Date: Sat, 2 Dec 2023 17:03:27 +0100 Subject: [PATCH 10/10] add gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5d6d5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +*~ +\#* +.\#*