From 374f355004a539ffc96afb209645f731f8844cf0 Mon Sep 17 00:00:00 2001 From: chewxy Date: Wed, 27 Dec 2017 19:17:08 +1100 Subject: [PATCH 01/10] Added function to create Subs --- substitutions.go | 7 +++++++ type.go | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/substitutions.go b/substitutions.go index a21dd11..546d052 100644 --- a/substitutions.go +++ b/substitutions.go @@ -14,6 +14,13 @@ type Subs interface { Clone() Subs } +func MakeSubs(n int) Subs { + if n >= 30 { + return make(mSubs) + } + return newSliceSubs(n) +} + // A Substitution is a tuple representing the TypeVariable and the replacement Type type Substitution struct { Tv TypeVariable diff --git a/type.go b/type.go index 6d2a1bc..384687e 100644 --- a/type.go +++ b/type.go @@ -7,10 +7,10 @@ import ( // Type represents all the possible type constructors. type Type interface { Substitutable - Name() string // Name is the name of the constructor - Normalize(TypeVarSet, TypeVarSet) (Type, error) // Normalize normalizes all the type variable names in the type - Types() Types // If the type is made up of smaller types, then it will return them - Eq(Type) bool // equality operation + Name() string // Name is the name of the constructor + Normalize(k TypeVarSet, v TypeVarSet) (Type, error) // Normalize normalizes all the type variable names in the type + Types() Types // If the type is made up of smaller types, then it will return them + Eq(Type) bool // equality operation fmt.Formatter fmt.Stringer From dfec7d229fecd503dc3ec0fc31dba08786a4b924 Mon Sep 17 00:00:00 2001 From: chewxy Date: Wed, 27 Dec 2017 19:17:35 +1100 Subject: [PATCH 02/10] Added a built-in types package, basically refactoring out bits and pieces of the parent package --- types/commonutils.go | 36 ++++++++++++ types/function.go | 121 +++++++++++++++++++++++++++++++++++++++++ types/function_test.go | 99 +++++++++++++++++++++++++++++++++ types/interfaces.go | 5 ++ types/perf.go | 31 +++++++++++ types/perf_test.go | 19 +++++++ types/record.go | 107 ++++++++++++++++++++++++++++++++++++ types/test_test.go | 42 ++++++++++++++ 8 files changed, 460 insertions(+) create mode 100644 types/commonutils.go create mode 100644 types/function.go create mode 100644 types/function_test.go create mode 100644 types/interfaces.go create mode 100644 types/perf.go create mode 100644 types/perf_test.go create mode 100644 types/record.go create mode 100644 types/test_test.go diff --git a/types/commonutils.go b/types/commonutils.go new file mode 100644 index 0000000..87802a1 --- /dev/null +++ b/types/commonutils.go @@ -0,0 +1,36 @@ +package hmtypes + +import "github.com/chewxy/hm" + +// Pair is a convenient structural abstraction for types that are composed of two types. +// Depending on use cases, it may be useful to embed Pair, or define a new type base on *Pair. +// +// Pair partially implements hm.Substitutable, but with very specific semantics - +// it's useful for a small subset of types like function types, or supertypes. +// See the documentation for Apply and FreeTypeVar. +type Pair struct { + A, B hm.Type +} + +// Apply applies a substitution on both the first and second types of the Pair. +func (t *Pair) Apply(sub hm.Subs) { + t.A = t.A.Apply(sub).(hm.Type) + t.B = t.B.Apply(sub).(hm.Type) +} + +// FreeTypeVar returns a set of free (unbound) type variables. +func (t Pair) FreeTypeVar() hm.TypeVarSet { return t.A.FreeTypeVar().Union(t.B.FreeTypeVar()) } + +// Monuple is a convenient structural abstraction for types that are composed of one type. +// +// Monuple implements hm.Substitutable, but with very specific semantics - +// It's useful for singly polymorphic types like arrays, linear types, reference types, etc +type Monuple struct { + A hm.Type +} + +// Apply applies a substitution to the monuple type. +func (t Monuple) Apply(subs hm.Subs) hm.Substitutable { t.A = t.A.Apply(subs).(hm.Type); return t } + +// FreeTypeVar returns the set of free type variables in the monuple. +func (t Monuple) FreeTypeVar() hm.TypeVarSet { return t.A.FreeTypeVar() } diff --git a/types/function.go b/types/function.go new file mode 100644 index 0000000..6dc0ec2 --- /dev/null +++ b/types/function.go @@ -0,0 +1,121 @@ +package hmtypes + +import ( + "fmt" + + "github.com/chewxy/hm" +) + +// FunctionType is a type constructor that builds function types. +type FunctionType Pair + +// NewFnType creates a new FunctionType. Functions are by default right associative. This: +// NewFnType(a, a, a) +// is short hand for this: +// NewFnType(a, NewFnType(a, a)) +func NewFnType(ts ...hm.Type) *FunctionType { + if len(ts) < 2 { + panic("Expected at least 2 input types") + } + + retVal := borrowFnType() + retVal.A = ts[0] + + if len(ts) > 2 { + retVal.B = NewFnType(ts[1:]...) + } else { + retVal.B = ts[1] + } + return retVal +} + +func (t *FunctionType) Name() string { return "→" } +func (t *FunctionType) Apply(sub hm.Subs) hm.Substitutable { ((*Pair)(t)).Apply(sub); return t } +func (t *FunctionType) FreeTypeVar() hm.TypeVarSet { return ((*Pair)(t)).FreeTypeVar() } +func (t *FunctionType) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%v → %v", t.A, t.B) } +func (t *FunctionType) String() string { return fmt.Sprintf("%v", t) } +func (t *FunctionType) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { + var a, b hm.Type + var err error + if a, err = t.A.Normalize(k, v); err != nil { + return nil, err + } + + if b, err = t.B.Normalize(k, v); err != nil { + return nil, err + } + + return NewFnType(a, b), nil +} +func (t *FunctionType) Types() hm.Types { + retVal := hm.BorrowTypes(2) + retVal[0] = t.A + retVal[1] = t.B + return retVal +} + +func (t *FunctionType) Eq(other hm.Type) bool { + if ot, ok := other.(*FunctionType); ok { + return ot.A.Eq(t.A) && ot.B.Eq(t.B) + } + return false +} + +// Other methods (accessors mainly) + +// Arg returns the type of the function argument +func (t *FunctionType) Arg() hm.Type { return t.A } + +// Ret returns the return type of a function. If recursive is true, it will get the final return type +func (t *FunctionType) Ret(recursive bool) hm.Type { + if !recursive { + return t.B + } + + if fnt, ok := t.B.(*FunctionType); ok { + return fnt.Ret(recursive) + } + + return t.B +} + +// FlatTypes returns the types in FunctionTypes as a flat slice of types. This allows for easier iteration in some applications +func (t *FunctionType) FlatTypes() hm.Types { + retVal := hm.BorrowTypes(8) // start with 8. Can always grow + retVal = retVal[:0] + + if a, ok := t.A.(*FunctionType); ok { + ft := a.FlatTypes() + retVal = append(retVal, ft...) + hm.ReturnTypes(ft) + } else { + retVal = append(retVal, t.A) + } + + if b, ok := t.B.(*FunctionType); ok { + ft := b.FlatTypes() + retVal = append(retVal, ft...) + hm.ReturnTypes(ft) + } else { + retVal = append(retVal, t.B) + } + return retVal +} + +// Clone implements Cloner +func (t *FunctionType) Clone() interface{} { + retVal := new(FunctionType) + + if ac, ok := t.A.(Cloner); ok { + retVal.A = ac.Clone().(hm.Type) + } else { + retVal.A = t.A + } + + if bc, ok := t.B.(Cloner); ok { + retVal.B = bc.Clone().(hm.Type) + } else { + retVal.B = t.B + } + return retVal +} diff --git a/types/function_test.go b/types/function_test.go new file mode 100644 index 0000000..93acb89 --- /dev/null +++ b/types/function_test.go @@ -0,0 +1,99 @@ +package hmtypes + +import ( + "testing" + + "github.com/chewxy/hm" + "github.com/stretchr/testify/assert" +) + +func TestFunctionTypeBasics(t *testing.T) { + fnType := NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a'), hm.TypeVariable('a')) + if fnType.Name() != "→" { + t.Errorf("FunctionType should have \"→\" as a name. Got %q instead", fnType.Name()) + } + + if fnType.String() != "a → a → a" { + t.Errorf("Expected \"a → a → a\". Got %q instead", fnType.String()) + } + + if !fnType.Arg().Eq(hm.TypeVariable('a')) { + t.Error("Expected arg of function to be 'a'") + } + + if !fnType.Ret(false).Eq(NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a'))) { + t.Error("Expected ret(false) to be a → a") + } + + if !fnType.Ret(true).Eq(hm.TypeVariable('a')) { + t.Error("Expected final return type to be 'a'") + } + + // a very simple fn + fnType = NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a')) + if !fnType.Ret(true).Eq(hm.TypeVariable('a')) { + t.Error("Expected final return type to be 'a'") + } + + ftv := fnType.FreeTypeVar() + if len(ftv) != 1 { + t.Errorf("Expected only one free type var") + } + + for _, fas := range fnApplyTests { + fn := fas.fn.Apply(fas.sub).(*FunctionType) + if !fn.Eq(fas.expected) { + t.Errorf("Expected %v. Got %v instead", fas.expected, fn) + } + } + + // bad shit + f := func() { + NewFnType(hm.TypeVariable('a')) + } + assert.Panics(t, f) +} + +var fnApplyTests = []struct { + fn *FunctionType + sub hm.Subs + + expected *FunctionType +}{ + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, proton)}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, neutron)}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'c': proton, 'd': neutron}, NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'))}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'c': neutron}, NewFnType(proton, hm.TypeVariable('b'))}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'c': proton, 'b': neutron}, NewFnType(hm.TypeVariable('a'), neutron)}, + {NewFnType(electron, proton), mSubs{'a': proton, 'b': neutron}, NewFnType(electron, proton)}, + + // a -> (b -> c) + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, neutron, proton)}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, proton, neutron)}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, neutron, hm.TypeVariable('c'))}, + {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('c'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, hm.TypeVariable('c'), neutron)}, + + // (a -> b) -> c + {NewFnType(NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFnType(NewFnType(proton, neutron), proton)}, +} + +func TestFunctionType_FlatTypes(t *testing.T) { + fnType := NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')) + ts := fnType.FlatTypes() + correct := hm.Types{hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')} + assert.Equal(t, ts, correct) + + fnType2 := NewFnType(fnType, hm.TypeVariable('d')) + correct = append(correct, hm.TypeVariable('d')) + ts = fnType2.FlatTypes() + assert.Equal(t, ts, correct) +} + +func TestFunctionType_Clone(t *testing.T) { + fnType := NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')) + assert.Equal(t, fnType.Clone(), fnType) + + rec := NewRecordType("", hm.TypeVariable('a'), NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), hm.TypeVariable('c')) + fnType = NewFnType(rec, rec) + assert.Equal(t, fnType.Clone(), fnType) +} diff --git a/types/interfaces.go b/types/interfaces.go new file mode 100644 index 0000000..b0afe8a --- /dev/null +++ b/types/interfaces.go @@ -0,0 +1,5 @@ +package hmtypes + +type Cloner interface { + Clone() interface{} +} diff --git a/types/perf.go b/types/perf.go new file mode 100644 index 0000000..6b369ed --- /dev/null +++ b/types/perf.go @@ -0,0 +1,31 @@ +package hmtypes + +import ( + "sync" + "unsafe" +) + +var pairPool = &sync.Pool{ + New: func() interface{} { return new(Pair) }, +} + +func borrowFnType() *FunctionType { + got := pairPool.Get().(*Pair) + return (*FunctionType)(unsafe.Pointer(got)) +} + +// ReturnFnType returns a *FunctionType to the pool. NewFnType automatically borrows from the pool. USE WITH CAUTION +func ReturnFnType(fnt *FunctionType) { + if a, ok := fnt.A.(*FunctionType); ok { + ReturnFnType(a) + } + + if b, ok := fnt.B.(*FunctionType); ok { + ReturnFnType(b) + } + + fnt.A = nil + fnt.B = nil + p := (*Pair)(unsafe.Pointer(fnt)) + pairPool.Put(p) +} diff --git a/types/perf_test.go b/types/perf_test.go new file mode 100644 index 0000000..8572a2e --- /dev/null +++ b/types/perf_test.go @@ -0,0 +1,19 @@ +package hmtypes + +import "testing" + +func TestFnTypePool(t *testing.T) { + f := borrowFnType() + f.A = NewFnType(proton, electron) + f.B = NewFnType(proton, neutron) + + ReturnFnType(f) + f = borrowFnType() + if f.A != nil { + t.Error("FunctionType not cleaned up: a is not nil") + } + if f.B != nil { + t.Error("FunctionType not cleaned up: b is not nil") + } + +} diff --git a/types/record.go b/types/record.go new file mode 100644 index 0000000..6e19ab0 --- /dev/null +++ b/types/record.go @@ -0,0 +1,107 @@ +package hmtypes + +import ( + "fmt" + + "github.com/chewxy/hm" +) + +// Record is a basic record/tuple type. It takes an optional name. +type Record struct { + ts []hm.Type + name string +} + +// NewRecordType creates a new Record hm.Type +func NewRecordType(name string, ts ...hm.Type) *Record { + return &Record{ + ts: ts, + name: name, + } +} + +func (t *Record) Apply(subs hm.Subs) hm.Substitutable { + ts := make([]hm.Type, len(t.ts)) + for i, v := range t.ts { + ts[i] = v.Apply(subs).(hm.Type) + } + return NewRecordType(t.name, ts...) +} + +func (t *Record) FreeTypeVar() hm.TypeVarSet { + var tvs hm.TypeVarSet + for _, v := range t.ts { + tvs = v.FreeTypeVar().Union(tvs) + } + return tvs +} + +func (t *Record) Name() string { + if t.name != "" { + return t.name + } + return t.String() +} + +func (t *Record) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { + ts := make([]hm.Type, len(t.ts)) + var err error + for i, tt := range t.ts { + if ts[i], err = tt.Normalize(k, v); err != nil { + return nil, err + } + } + return NewRecordType(t.name, ts...), nil +} + +func (t *Record) Types() hm.Types { + ts := hm.BorrowTypes(len(t.ts)) + copy(ts, t.ts) + return ts +} + +func (t *Record) Eq(other hm.Type) bool { + if ot, ok := other.(*Record); ok { + if len(ot.ts) != len(t.ts) { + return false + } + for i, v := range t.ts { + if !v.Eq(ot.ts[i]) { + return false + } + } + return true + } + return false +} + +func (t *Record) Format(f fmt.State, c rune) { + f.Write([]byte("(")) + for i, v := range t.ts { + if i < len(t.ts)-1 { + fmt.Fprintf(f, "%v, ", v) + } else { + fmt.Fprintf(f, "%v)", v) + } + } + +} + +func (t *Record) String() string { return fmt.Sprintf("%v", t) } + +// Clone implements Cloner +func (t *Record) Clone() interface{} { + retVal := new(Record) + ts := hm.BorrowTypes(len(t.ts)) + for i, tt := range t.ts { + if c, ok := tt.(Cloner); ok { + ts[i] = c.Clone().(hm.Type) + } else { + ts[i] = tt + } + } + retVal.ts = ts + retVal.name = t.name + + return retVal +} diff --git a/types/test_test.go b/types/test_test.go new file mode 100644 index 0000000..c3a67ba --- /dev/null +++ b/types/test_test.go @@ -0,0 +1,42 @@ +package hmtypes + +import "github.com/chewxy/hm" + +const ( + proton hm.TypeConst = "proton" + neutron hm.TypeConst = "neutron" + quark hm.TypeConst = "quark" + + electron hm.TypeConst = "electron" + positron hm.TypeConst = "positron" + muon hm.TypeConst = "muon" + + photon hm.TypeConst = "photon" + higgs hm.TypeConst = "higgs" +) + +// useful copy pasta from the hm package +type mSubs map[hm.TypeVariable]hm.Type + +func (s mSubs) Get(tv hm.TypeVariable) (hm.Type, bool) { retVal, ok := s[tv]; return retVal, ok } +func (s mSubs) Add(tv hm.TypeVariable, t hm.Type) hm.Subs { s[tv] = t; return s } +func (s mSubs) Remove(tv hm.TypeVariable) hm.Subs { delete(s, tv); return s } + +func (s mSubs) Iter() []hm.Substitution { + retVal := make([]hm.Substitution, len(s)) + var i int + for k, v := range s { + retVal[i] = hm.Substitution{k, v} + i++ + } + return retVal +} + +func (s mSubs) Size() int { return len(s) } +func (s mSubs) Clone() hm.Subs { + retVal := make(mSubs) + for k, v := range s { + retVal[k] = v + } + return retVal +} From 6b26671f8cd039db0d04d30d3d57f228eebd0f67 Mon Sep 17 00:00:00 2001 From: chewxy Date: Wed, 27 Dec 2017 19:20:09 +1100 Subject: [PATCH 03/10] Updated variable naming scheme --- typeVariable.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/typeVariable.go b/typeVariable.go index 2d33945..6ea1d4c 100644 --- a/typeVariable.go +++ b/typeVariable.go @@ -9,7 +9,7 @@ import ( // TypeVariable is a variable that ranges over the types - that is to say it can take any type. type TypeVariable rune -func (t TypeVariable) Name() string { return string(t) } +func (t TypeVariable) Name() string { return fmt.Sprintf("%v", t) } func (t TypeVariable) Apply(sub Subs) Substitutable { if sub == nil { return t @@ -30,7 +30,13 @@ func (t TypeVariable) Normalize(k, v TypeVarSet) (Type, error) { return nil, errors.Errorf("Type Variable %v not in signature", t) } -func (t TypeVariable) Types() Types { return nil } -func (t TypeVariable) String() string { return string(t) } -func (t TypeVariable) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%c", rune(t)) } -func (t TypeVariable) Eq(other Type) bool { return other == t } +func (t TypeVariable) Types() Types { return nil } +func (t TypeVariable) String() string { return fmt.Sprintf("%v", t) } +func (t TypeVariable) Format(s fmt.State, c rune) { + if t >= 'a' && t <= 'z' { + fmt.Fprintf(s, "%c", t) + return + } + fmt.Fprintf(s, "<%d>", t) +} +func (t TypeVariable) Eq(other Type) bool { return other == t } From f3d66733fceea2488a3ed024ae722bea847ddad5 Mon Sep 17 00:00:00 2001 From: chewxy Date: Wed, 27 Dec 2017 21:00:26 +1100 Subject: [PATCH 04/10] Added Monuples --- typeVariable.go | 4 +-- types/commonutils.go | 52 ++++++++++++++++++++++++++--- types/function.go | 74 +++++++++++++++++------------------------- types/function_test.go | 46 +++++++++++++------------- types/monuples.go | 55 +++++++++++++++++++++++++++++++ types/perf.go | 20 +++++++----- types/perf_test.go | 10 +++--- 7 files changed, 173 insertions(+), 88 deletions(-) create mode 100644 types/monuples.go diff --git a/typeVariable.go b/typeVariable.go index 6ea1d4c..1fc383e 100644 --- a/typeVariable.go +++ b/typeVariable.go @@ -34,9 +34,9 @@ func (t TypeVariable) Types() Types { return nil } func (t TypeVariable) String() string { return fmt.Sprintf("%v", t) } func (t TypeVariable) Format(s fmt.State, c rune) { if t >= 'a' && t <= 'z' { - fmt.Fprintf(s, "%c", t) + fmt.Fprintf(s, "%c", rune(t)) return } - fmt.Fprintf(s, "<%d>", t) + fmt.Fprintf(s, "<%d>", rune(t)) } func (t TypeVariable) Eq(other Type) bool { return other == t } diff --git a/types/commonutils.go b/types/commonutils.go index 87802a1..61f16c3 100644 --- a/types/commonutils.go +++ b/types/commonutils.go @@ -5,7 +5,9 @@ import "github.com/chewxy/hm" // Pair is a convenient structural abstraction for types that are composed of two types. // Depending on use cases, it may be useful to embed Pair, or define a new type base on *Pair. // -// Pair partially implements hm.Substitutable, but with very specific semantics - +// Pair partially implements hm.Type, as the intention is merely for syntactic abstraction +// +// It has very specific semantics - // it's useful for a small subset of types like function types, or supertypes. // See the documentation for Apply and FreeTypeVar. type Pair struct { @@ -18,19 +20,59 @@ func (t *Pair) Apply(sub hm.Subs) { t.B = t.B.Apply(sub).(hm.Type) } +// Types returns all the types of the Pair's constituents +func (t *Pair) Types() hm.Types { + retVal := hm.BorrowTypes(2) + retVal[0] = t.A + retVal[1] = t.B + return retVal +} + // FreeTypeVar returns a set of free (unbound) type variables. -func (t Pair) FreeTypeVar() hm.TypeVarSet { return t.A.FreeTypeVar().Union(t.B.FreeTypeVar()) } +func (t *Pair) FreeTypeVar() hm.TypeVarSet { return t.A.FreeTypeVar().Union(t.B.FreeTypeVar()) } + +// Clone implements Cloner +func (t *Pair) Clone() *Pair { + retVal := borrowPair() + + if ac, ok := t.A.(Cloner); ok { + retVal.A = ac.Clone().(hm.Type) + } else { + retVal.A = t.A + } + + if bc, ok := t.B.(Cloner); ok { + retVal.B = bc.Clone().(hm.Type) + } else { + retVal.B = t.B + } + return retVal +} // Monuple is a convenient structural abstraction for types that are composed of one type. // // Monuple implements hm.Substitutable, but with very specific semantics - // It's useful for singly polymorphic types like arrays, linear types, reference types, etc type Monuple struct { - A hm.Type + T hm.Type } // Apply applies a substitution to the monuple type. -func (t Monuple) Apply(subs hm.Subs) hm.Substitutable { t.A = t.A.Apply(subs).(hm.Type); return t } +func (t Monuple) Apply(subs hm.Subs) Monuple { + t.T = t.T.Apply(subs).(hm.Type) + return t +} // FreeTypeVar returns the set of free type variables in the monuple. -func (t Monuple) FreeTypeVar() hm.TypeVarSet { return t.A.FreeTypeVar() } +func (t Monuple) FreeTypeVar() hm.TypeVarSet { return t.T.FreeTypeVar() } + +// Normalize is the method to normalize all type variables +func (t Monuple) Normalize(k, v hm.TypeVarSet) (Monuple, error) { + var t2 hm.Type + var err error + if t2, err = t.T.Normalize(k, v); err != nil { + return nil, err + } + t.T = t2 + return t, nil +} diff --git a/types/function.go b/types/function.go index 6dc0ec2..a727dd3 100644 --- a/types/function.go +++ b/types/function.go @@ -6,35 +6,35 @@ import ( "github.com/chewxy/hm" ) -// FunctionType is a type constructor that builds function types. -type FunctionType Pair +// Function is a type constructor that builds function types. +type Function Pair -// NewFnType creates a new FunctionType. Functions are by default right associative. This: -// NewFnType(a, a, a) +// NewFunction creates a new FunctionType. Functions are by default right associative. This: +// NewFunction(a, a, a) // is short hand for this: -// NewFnType(a, NewFnType(a, a)) -func NewFnType(ts ...hm.Type) *FunctionType { +// NewFunction(a, NewFunction(a, a)) +func NewFunction(ts ...hm.Type) *Function { if len(ts) < 2 { panic("Expected at least 2 input types") } - retVal := borrowFnType() + retVal := borrowFn() retVal.A = ts[0] if len(ts) > 2 { - retVal.B = NewFnType(ts[1:]...) + retVal.B = NewFunction(ts[1:]...) } else { retVal.B = ts[1] } return retVal } -func (t *FunctionType) Name() string { return "→" } -func (t *FunctionType) Apply(sub hm.Subs) hm.Substitutable { ((*Pair)(t)).Apply(sub); return t } -func (t *FunctionType) FreeTypeVar() hm.TypeVarSet { return ((*Pair)(t)).FreeTypeVar() } -func (t *FunctionType) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%v → %v", t.A, t.B) } -func (t *FunctionType) String() string { return fmt.Sprintf("%v", t) } -func (t *FunctionType) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { +func (t *Function) Name() string { return "→" } +func (t *Function) Apply(sub hm.Subs) hm.Substitutable { ((*Pair)(t)).Apply(sub); return t } +func (t *Function) FreeTypeVar() hm.TypeVarSet { return ((*Pair)(t)).FreeTypeVar() } +func (t *Function) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%v → %v", t.A, t.B) } +func (t *Function) String() string { return fmt.Sprintf("%v", t) } +func (t *Function) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { var a, b hm.Type var err error if a, err = t.A.Normalize(k, v); err != nil { @@ -45,17 +45,12 @@ func (t *FunctionType) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { return nil, err } - return NewFnType(a, b), nil -} -func (t *FunctionType) Types() hm.Types { - retVal := hm.BorrowTypes(2) - retVal[0] = t.A - retVal[1] = t.B - return retVal + return NewFunction(a, b), nil } +func (t *Function) Types() hm.Types { return ((*Pair)(t)).Types() } -func (t *FunctionType) Eq(other hm.Type) bool { - if ot, ok := other.(*FunctionType); ok { +func (t *Function) Eq(other hm.Type) bool { + if ot, ok := other.(*Function); ok { return ot.A.Eq(t.A) && ot.B.Eq(t.B) } return false @@ -64,15 +59,15 @@ func (t *FunctionType) Eq(other hm.Type) bool { // Other methods (accessors mainly) // Arg returns the type of the function argument -func (t *FunctionType) Arg() hm.Type { return t.A } +func (t *Function) Arg() hm.Type { return t.A } // Ret returns the return type of a function. If recursive is true, it will get the final return type -func (t *FunctionType) Ret(recursive bool) hm.Type { +func (t *Function) Ret(recursive bool) hm.Type { if !recursive { return t.B } - if fnt, ok := t.B.(*FunctionType); ok { + if fnt, ok := t.B.(*Function); ok { return fnt.Ret(recursive) } @@ -80,11 +75,11 @@ func (t *FunctionType) Ret(recursive bool) hm.Type { } // FlatTypes returns the types in FunctionTypes as a flat slice of types. This allows for easier iteration in some applications -func (t *FunctionType) FlatTypes() hm.Types { +func (t *Function) FlatTypes() hm.Types { retVal := hm.BorrowTypes(8) // start with 8. Can always grow retVal = retVal[:0] - if a, ok := t.A.(*FunctionType); ok { + if a, ok := t.A.(*Function); ok { ft := a.FlatTypes() retVal = append(retVal, ft...) hm.ReturnTypes(ft) @@ -92,7 +87,7 @@ func (t *FunctionType) FlatTypes() hm.Types { retVal = append(retVal, t.A) } - if b, ok := t.B.(*FunctionType); ok { + if b, ok := t.B.(*Function); ok { ft := b.FlatTypes() retVal = append(retVal, ft...) hm.ReturnTypes(ft) @@ -102,20 +97,9 @@ func (t *FunctionType) FlatTypes() hm.Types { return retVal } -// Clone implements Cloner -func (t *FunctionType) Clone() interface{} { - retVal := new(FunctionType) - - if ac, ok := t.A.(Cloner); ok { - retVal.A = ac.Clone().(hm.Type) - } else { - retVal.A = t.A - } - - if bc, ok := t.B.(Cloner); ok { - retVal.B = bc.Clone().(hm.Type) - } else { - retVal.B = t.B - } - return retVal +// Clone implenents cloner +func (t *Function) Clone() interface{} { + p := (*Pair)(t) + cloned := p.Clone() + return (*Function)(cloned) } diff --git a/types/function_test.go b/types/function_test.go index 93acb89..4e28fa4 100644 --- a/types/function_test.go +++ b/types/function_test.go @@ -8,7 +8,7 @@ import ( ) func TestFunctionTypeBasics(t *testing.T) { - fnType := NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a'), hm.TypeVariable('a')) + fnType := NewFunction(hm.TypeVariable('a'), hm.TypeVariable('a'), hm.TypeVariable('a')) if fnType.Name() != "→" { t.Errorf("FunctionType should have \"→\" as a name. Got %q instead", fnType.Name()) } @@ -21,7 +21,7 @@ func TestFunctionTypeBasics(t *testing.T) { t.Error("Expected arg of function to be 'a'") } - if !fnType.Ret(false).Eq(NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a'))) { + if !fnType.Ret(false).Eq(NewFunction(hm.TypeVariable('a'), hm.TypeVariable('a'))) { t.Error("Expected ret(false) to be a → a") } @@ -30,7 +30,7 @@ func TestFunctionTypeBasics(t *testing.T) { } // a very simple fn - fnType = NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a')) + fnType = NewFunction(hm.TypeVariable('a'), hm.TypeVariable('a')) if !fnType.Ret(true).Eq(hm.TypeVariable('a')) { t.Error("Expected final return type to be 'a'") } @@ -41,7 +41,7 @@ func TestFunctionTypeBasics(t *testing.T) { } for _, fas := range fnApplyTests { - fn := fas.fn.Apply(fas.sub).(*FunctionType) + fn := fas.fn.Apply(fas.sub).(*Function) if !fn.Eq(fas.expected) { t.Errorf("Expected %v. Got %v instead", fas.expected, fn) } @@ -49,51 +49,51 @@ func TestFunctionTypeBasics(t *testing.T) { // bad shit f := func() { - NewFnType(hm.TypeVariable('a')) + NewFunction(hm.TypeVariable('a')) } assert.Panics(t, f) } var fnApplyTests = []struct { - fn *FunctionType + fn *Function sub hm.Subs - expected *FunctionType + expected *Function }{ - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, proton)}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, neutron)}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'c': proton, 'd': neutron}, NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'))}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'c': neutron}, NewFnType(proton, hm.TypeVariable('b'))}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'c': proton, 'b': neutron}, NewFnType(hm.TypeVariable('a'), neutron)}, - {NewFnType(electron, proton), mSubs{'a': proton, 'b': neutron}, NewFnType(electron, proton)}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFunction(proton, proton)}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFunction(proton, neutron)}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'c': proton, 'd': neutron}, NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b'))}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'c': neutron}, NewFunction(proton, hm.TypeVariable('b'))}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'c': proton, 'b': neutron}, NewFunction(hm.TypeVariable('a'), neutron)}, + {NewFunction(electron, proton), mSubs{'a': proton, 'b': neutron}, NewFunction(electron, proton)}, // a -> (b -> c) - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, neutron, proton)}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, proton, neutron)}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, neutron, hm.TypeVariable('c'))}, - {NewFnType(hm.TypeVariable('a'), hm.TypeVariable('c'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFnType(proton, hm.TypeVariable('c'), neutron)}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFunction(proton, neutron, proton)}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('a'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFunction(proton, proton, neutron)}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')), mSubs{'a': proton, 'b': neutron}, NewFunction(proton, neutron, hm.TypeVariable('c'))}, + {NewFunction(hm.TypeVariable('a'), hm.TypeVariable('c'), hm.TypeVariable('b')), mSubs{'a': proton, 'b': neutron}, NewFunction(proton, hm.TypeVariable('c'), neutron)}, // (a -> b) -> c - {NewFnType(NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFnType(NewFnType(proton, neutron), proton)}, + {NewFunction(NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b')), hm.TypeVariable('a')), mSubs{'a': proton, 'b': neutron}, NewFunction(NewFunction(proton, neutron), proton)}, } func TestFunctionType_FlatTypes(t *testing.T) { - fnType := NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')) + fnType := NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')) ts := fnType.FlatTypes() correct := hm.Types{hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')} assert.Equal(t, ts, correct) - fnType2 := NewFnType(fnType, hm.TypeVariable('d')) + fnType2 := NewFunction(fnType, hm.TypeVariable('d')) correct = append(correct, hm.TypeVariable('d')) ts = fnType2.FlatTypes() assert.Equal(t, ts, correct) } func TestFunctionType_Clone(t *testing.T) { - fnType := NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')) + fnType := NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c')) assert.Equal(t, fnType.Clone(), fnType) - rec := NewRecordType("", hm.TypeVariable('a'), NewFnType(hm.TypeVariable('a'), hm.TypeVariable('b')), hm.TypeVariable('c')) - fnType = NewFnType(rec, rec) + rec := NewRecordType("", hm.TypeVariable('a'), NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b')), hm.TypeVariable('c')) + fnType = NewFunction(rec, rec) assert.Equal(t, fnType.Clone(), fnType) } diff --git a/types/monuples.go b/types/monuples.go new file mode 100644 index 0000000..ba507e2 --- /dev/null +++ b/types/monuples.go @@ -0,0 +1,55 @@ +package hmtypes + +import ( + "fmt" + + "github.com/chewxy/hm" +) + +// Slice is the type of a Slice/List +type Slice Monuple + +func (t Slice) Name() string { return "List" } +func (t Slice) Apply(subs hm.Subs) hm.Substitutable { return Slice(Monuple(t).Apply(subs)) } +func (t Slice) FreeTypeVar() hm.TypeVarSet { return Monuple(t).FreeTypeVar() } +func (t Slice) Format(s fmt.State, c rune) { fmt.Fprintf(s, "[]%v", t.T) } +func (t Slice) String() string { return fmt.Sprintf("%v", t) } +func (t Slice) Types() hm.Types { return hm.Types{t.T} } +func (t Slice) Eq(other hm.Type) bool { + if ot, ok := other.(Slice); ok { + return ot.T.Eq(t.T) + } + return false +} + +// Linear is a linear type (i.e types that can only appear once) +type Linear Monuple + +func (t Linear) Name() string { return "Linear" } +func (t Linear) Apply(subs hm.Subs) hm.Substitutable { return Linear(Monuple(t).Apply(subs)) } +func (t Linear) FreeTypeVar() hm.TypeVarSet { return Monuple(t).FreeTypeVar() } +func (t Linear) Format(s fmt.State, c rune) { fmt.Fprintf(s, "Linear[%v]", t.T) } +func (t Linear) String() string { return fmt.Sprintf("%v", t) } +func (t Linear) Types() hm.Types { return hm.Types{t.T} } +func (t Linear) Eq(other hm.Type) bool { + if ot, ok := other.(Linear); ok { + return ot.T.Eq(t.T) + } + return false +} + +// Ref is a reference type (think pointers) +type Ref Monuple + +func (t Ref) Name() string { return "Ref" } +func (t Ref) Apply(subs hm.Subs) hm.Substitutable { return Ref(Monuple(t).Apply(subs)) } +func (t Ref) FreeTypeVar() hm.TypeVarSet { return Monuple(t).FreeTypeVar() } +func (t Ref) Format(s fmt.State, c rune) { fmt.Fprintf(s, "*%v", t.T) } +func (t Ref) String() string { return fmt.Sprintf("%v", t) } +func (t Ref) Types() hm.Types { return hm.Types{t.T} } +func (t Ref) Eq(other hm.Type) bool { + if ot, ok := other.(Ref); ok { + return ot.T.Eq(t.T) + } + return false +} diff --git a/types/perf.go b/types/perf.go index 6b369ed..e682236 100644 --- a/types/perf.go +++ b/types/perf.go @@ -9,19 +9,23 @@ var pairPool = &sync.Pool{ New: func() interface{} { return new(Pair) }, } -func borrowFnType() *FunctionType { +func borrowPair() *Pair { + return pairPool.Get().(*Pair) +} + +func borrowFn() *Function { got := pairPool.Get().(*Pair) - return (*FunctionType)(unsafe.Pointer(got)) + return (*Function)(unsafe.Pointer(got)) } -// ReturnFnType returns a *FunctionType to the pool. NewFnType automatically borrows from the pool. USE WITH CAUTION -func ReturnFnType(fnt *FunctionType) { - if a, ok := fnt.A.(*FunctionType); ok { - ReturnFnType(a) +// ReturnFn returns a *FunctionType to the pool. NewFnType automatically borrows from the pool. USE WITH CAUTION +func ReturnFn(fnt *Function) { + if a, ok := fnt.A.(*Function); ok { + ReturnFn(a) } - if b, ok := fnt.B.(*FunctionType); ok { - ReturnFnType(b) + if b, ok := fnt.B.(*Function); ok { + ReturnFn(b) } fnt.A = nil diff --git a/types/perf_test.go b/types/perf_test.go index 8572a2e..7d82844 100644 --- a/types/perf_test.go +++ b/types/perf_test.go @@ -3,12 +3,12 @@ package hmtypes import "testing" func TestFnTypePool(t *testing.T) { - f := borrowFnType() - f.A = NewFnType(proton, electron) - f.B = NewFnType(proton, neutron) + f := borrowFn() + f.A = NewFunction(proton, electron) + f.B = NewFunction(proton, neutron) - ReturnFnType(f) - f = borrowFnType() + ReturnFn(f) + f = borrowFn() if f.A != nil { t.Error("FunctionType not cleaned up: a is not nil") } From 0317cce5005e7a9f7fe99ace4897a2d82b2c2c87 Mon Sep 17 00:00:00 2001 From: chewxy Date: Thu, 28 Dec 2017 12:22:20 +1100 Subject: [PATCH 05/10] Added pair types and quantified types --- types/pairs.go | 33 +++++++++++++++++++++++++++++++++ types/quantified.go | 9 +++++++++ 2 files changed, 42 insertions(+) create mode 100644 types/pairs.go create mode 100644 types/quantified.go diff --git a/types/pairs.go b/types/pairs.go new file mode 100644 index 0000000..7c0cf30 --- /dev/null +++ b/types/pairs.go @@ -0,0 +1,33 @@ +package hmtypes + +// pair types + +// Choice is the type of choice of algorithm to use within a class method. +// +// Imagine how one would implement a class in an OOP language. +// Then imagine how one would implement method overloading for the class. +// The typical approach is name mangling followed by having a jump table. +// +// Now consider OOP classes and the ability to override methods, based on subclassing ability. +// The typical approach to this is to use a Vtable. +// +// Both overloading and overriding have a general notion: a jump table of sorts. +// How does one type such a table? +// +// By using Choice. +// +// The first type is the key of either the vtable or the name mangled table. +// The second type is the value of the table. +// +// TODO: implement hm.Type +type Choice Pair + +// Super is the inverse of Choice. It allows for supertyping functions. +// +// Supertyping is typically implemented as a adding an entry to the vtable/mangled table. +// But there needs to be a separate accounting structure to keep account of the types. +// +// This is where Super comes in. +// +// TODO: implement hm.Type +type Super Pair diff --git a/types/quantified.go b/types/quantified.go new file mode 100644 index 0000000..a54ae75 --- /dev/null +++ b/types/quantified.go @@ -0,0 +1,9 @@ +package hmtypes + +import "github.com/chewxy/hm" + +// Quantified is essentially a replacement scheme that is made into a Type +// TODO: implement hm.Type +type Quantified struct { + hm.Scheme +} From 71799abce23fb5bf41db6c9e2631f5ab3b6eb7fe Mon Sep 17 00:00:00 2001 From: chewxy Date: Thu, 28 Dec 2017 13:04:17 +1100 Subject: [PATCH 06/10] Added Normalize to the monuples --- types/commonutils.go | 2 +- types/monuples.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/types/commonutils.go b/types/commonutils.go index 61f16c3..5c1e736 100644 --- a/types/commonutils.go +++ b/types/commonutils.go @@ -71,7 +71,7 @@ func (t Monuple) Normalize(k, v hm.TypeVarSet) (Monuple, error) { var t2 hm.Type var err error if t2, err = t.T.Normalize(k, v); err != nil { - return nil, err + return Monuple{}, err } t.T = t2 return t, nil diff --git a/types/monuples.go b/types/monuples.go index ba507e2..48bb9ae 100644 --- a/types/monuples.go +++ b/types/monuples.go @@ -15,6 +15,15 @@ func (t Slice) FreeTypeVar() hm.TypeVarSet { return Monuple(t).FreeType func (t Slice) Format(s fmt.State, c rune) { fmt.Fprintf(s, "[]%v", t.T) } func (t Slice) String() string { return fmt.Sprintf("%v", t) } func (t Slice) Types() hm.Types { return hm.Types{t.T} } + +func (t Slice) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { + t2, err := Monuple(t).Normalize(k, v) + if err != nil { + return nil, err + } + return Slice(t2), nil +} + func (t Slice) Eq(other hm.Type) bool { if ot, ok := other.(Slice); ok { return ot.T.Eq(t.T) @@ -31,6 +40,15 @@ func (t Linear) FreeTypeVar() hm.TypeVarSet { return Monuple(t).FreeTyp func (t Linear) Format(s fmt.State, c rune) { fmt.Fprintf(s, "Linear[%v]", t.T) } func (t Linear) String() string { return fmt.Sprintf("%v", t) } func (t Linear) Types() hm.Types { return hm.Types{t.T} } + +func (t Linear) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { + t2, err := Monuple(t).Normalize(k, v) + if err != nil { + return nil, err + } + return Linear(t2), nil +} + func (t Linear) Eq(other hm.Type) bool { if ot, ok := other.(Linear); ok { return ot.T.Eq(t.T) @@ -47,6 +65,15 @@ func (t Ref) FreeTypeVar() hm.TypeVarSet { return Monuple(t).FreeTypeVa func (t Ref) Format(s fmt.State, c rune) { fmt.Fprintf(s, "*%v", t.T) } func (t Ref) String() string { return fmt.Sprintf("%v", t) } func (t Ref) Types() hm.Types { return hm.Types{t.T} } + +func (t Ref) Normalize(k, v hm.TypeVarSet) (hm.Type, error) { + t2, err := Monuple(t).Normalize(k, v) + if err != nil { + return nil, err + } + return Ref(t2), nil +} + func (t Ref) Eq(other hm.Type) bool { if ot, ok := other.(Ref); ok { return ot.T.Eq(t.T) From cc8b3e031ff6924a9fc02e03f662d88b53cbf0b7 Mon Sep 17 00:00:00 2001 From: chewxy Date: Thu, 28 Dec 2017 13:33:29 +1100 Subject: [PATCH 07/10] exported a few internal functions - they're useful in other unificationbased type systems that aren't strictly HM --- hm.go | 14 ++++++++------ solver.go | 2 +- substitutions.go | 3 ++- substitutions_test.go | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/hm.go b/hm.go index 5d8cc8b..39098c9 100644 --- a/hm.go +++ b/hm.go @@ -334,14 +334,14 @@ func Unify(a, b Type) (sub Subs, err error) { switch at := a.(type) { case TypeVariable: - return bind(at, b) + return Bind(at, b) default: if a.Eq(b) { return nil, nil } if btv, ok := b.(TypeVariable); ok { - return bind(btv, a) + return Bind(btv, a) } atypes := a.Types() btypes := b.Types() @@ -385,7 +385,7 @@ func unifyMany(a, b Types) (sub Subs, err error) { if sub == nil { sub = s2 } else { - sub2 := compose(sub, s2) + sub2 := Compose(sub, s2) defer ReturnSubs(s2) if sub2 != sub { defer ReturnSubs(sub) @@ -396,11 +396,12 @@ func unifyMany(a, b Types) (sub Subs, err error) { return } -func bind(tv TypeVariable, t Type) (sub Subs, err error) { +// Bind binds a TypeVariable to a Type. It returns a substitution list. +func Bind(tv TypeVariable, t Type) (sub Subs, err error) { logf("Binding %v to %v", tv, t) switch { // case tv == t: - case occurs(tv, t): + case Occurs(tv, t): err = errors.Errorf("recursive unification") default: ssub := BorrowSSubs(1) @@ -411,7 +412,8 @@ func bind(tv TypeVariable, t Type) (sub Subs, err error) { return } -func occurs(tv TypeVariable, s Substitutable) bool { +// Occurs checks if a TypeVariable exists in any Substitutable (type, scheme, map etc). +func Occurs(tv TypeVariable, s Substitutable) bool { ftv := s.FreeTypeVar() defer ReturnTypeVarSet(ftv) diff --git a/solver.go b/solver.go index 80a1142..3c055f0 100644 --- a/solver.go +++ b/solver.go @@ -27,7 +27,7 @@ func (s *solver) solve(cs Constraints) { sub, s.err = Unify(c.a, c.b) defer ReturnSubs(s.sub) - s.sub = compose(sub, s.sub) + s.sub = Compose(sub, s.sub) cs = cs[1:].Apply(s.sub).(Constraints) s.solve(cs) diff --git a/substitutions.go b/substitutions.go index 546d052..abd0b62 100644 --- a/substitutions.go +++ b/substitutions.go @@ -123,7 +123,8 @@ func (s mSubs) Clone() Subs { return retVal } -func compose(a, b Subs) (retVal Subs) { +// Compose composes two substitution lists together. +func Compose(a, b Subs) (retVal Subs) { if b == nil { return a } diff --git a/substitutions_test.go b/substitutions_test.go index aabbd14..9e467a0 100644 --- a/substitutions_test.go +++ b/substitutions_test.go @@ -131,7 +131,7 @@ var composeTests = []struct { func TestCompose(t *testing.T) { for i, cts := range composeTests { - subs := compose(cts.a, cts.b) + subs := Compose(cts.a, cts.b) for _, v := range cts.expected.Iter() { if T, ok := subs.Get(v.Tv); !ok { From 88fd6bdd4651c1862dbb3f21aa0838a41d441fe7 Mon Sep 17 00:00:00 2001 From: chewxy Date: Thu, 28 Dec 2017 19:24:04 +1100 Subject: [PATCH 08/10] Added Application, added comments to MakeSub --- substitutions.go | 2 ++ types/pairs.go | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/substitutions.go b/substitutions.go index abd0b62..3a85e25 100644 --- a/substitutions.go +++ b/substitutions.go @@ -14,6 +14,8 @@ type Subs interface { Clone() Subs } +// MakeSubs is a utility function to help make substitution lists. +// This is useful for cases where there isn't a real need to implement Subs func MakeSubs(n int) Subs { if n >= 30 { return make(mSubs) diff --git a/types/pairs.go b/types/pairs.go index 7c0cf30..9dc539b 100644 --- a/types/pairs.go +++ b/types/pairs.go @@ -31,3 +31,14 @@ type Choice Pair // // TODO: implement hm.Type type Super Pair + +// Application is the pre-unified type for a function application. +// In a simple HM system this would not be needed as the type of an +// application expression would be found during the unification phase of +// the expression. +// +// In advanced systems where unification may be done concurrently, this would +// be required, as a "thunk" of sorts for the type system. +// +// TODO: implement hm.Type +type Application Pair From 44a2cf0f344aeb1c32a065e90d674546e2583600 Mon Sep 17 00:00:00 2001 From: chewxy Date: Thu, 28 Dec 2017 19:36:44 +1100 Subject: [PATCH 09/10] Implemented Type for the Pair types. Except for Normalize --- types/pairs.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/types/pairs.go b/types/pairs.go index 9dc539b..e7d7008 100644 --- a/types/pairs.go +++ b/types/pairs.go @@ -1,5 +1,17 @@ package hmtypes +import ( + "fmt" + + "github.com/chewxy/hm" +) + +var ( + _ hm.Type = &Choice{} + _ hm.Type = &Super{} + _ hm.Type = &Application{} +) + // pair types // Choice is the type of choice of algorithm to use within a class method. @@ -18,20 +30,54 @@ package hmtypes // // The first type is the key of either the vtable or the name mangled table. // The second type is the value of the table. -// -// TODO: implement hm.Type type Choice Pair +func (t *Choice) Name() string { return ":" } +func (t *Choice) Apply(sub hm.Subs) hm.Substitutable { ((*Pair)(t)).Apply(sub); return t } +func (t *Choice) FreeTypeVar() hm.TypeVarSet { return ((*Pair)(t)).FreeTypeVar() } +func (t *Choice) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%v : %v", t.A, t.B) } +func (t *Choice) String() string { return fmt.Sprintf("%v", t) } + +func (t *Choice) Normalize(k hm.TypeVarSet, v hm.TypeVarSet) (hm.Type, error) { + panic("not implemented") +} + +func (t *Choice) Types() hm.Types { return ((*Pair)(t)).Types() } + +func (t *Choice) Eq(other hm.Type) bool { + if ot, ok := other.(*Choice); ok { + return ot.A.Eq(t.A) && ot.B.Eq(t.B) + } + return false +} + // Super is the inverse of Choice. It allows for supertyping functions. // // Supertyping is typically implemented as a adding an entry to the vtable/mangled table. // But there needs to be a separate accounting structure to keep account of the types. // // This is where Super comes in. -// -// TODO: implement hm.Type type Super Pair +func (t *Super) Name() string { return "§" } +func (t *Super) Apply(sub hm.Subs) hm.Substitutable { ((*Pair)(t)).Apply(sub); return t } +func (t *Super) FreeTypeVar() hm.TypeVarSet { return ((*Pair)(t)).FreeTypeVar() } +func (t *Super) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%v §: %v", t.A, t.B) } +func (t *Super) String() string { return fmt.Sprintf("%v", t) } + +func (t *Super) Normalize(k hm.TypeVarSet, v hm.TypeVarSet) (hm.Type, error) { + panic("not implemented") +} + +func (t *Super) Types() hm.Types { return ((*Pair)(t)).Types() } + +func (t *Super) Eq(other hm.Type) bool { + if ot, ok := other.(*Super); ok { + return ot.A.Eq(t.A) && ot.B.Eq(t.B) + } + return false +} + // Application is the pre-unified type for a function application. // In a simple HM system this would not be needed as the type of an // application expression would be found during the unification phase of @@ -39,6 +85,23 @@ type Super Pair // // In advanced systems where unification may be done concurrently, this would // be required, as a "thunk" of sorts for the type system. -// -// TODO: implement hm.Type type Application Pair + +func (t *Application) Name() string { return "•" } +func (t *Application) Apply(sub hm.Subs) hm.Substitutable { ((*Pair)(t)).Apply(sub); return t } +func (t *Application) FreeTypeVar() hm.TypeVarSet { return ((*Pair)(t)).FreeTypeVar() } +func (t *Application) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%v • %v", t.A, t.B) } +func (t *Application) String() string { return fmt.Sprintf("%v", t) } + +func (t *Application) Normalize(k hm.TypeVarSet, v hm.TypeVarSet) (hm.Type, error) { + panic("not implemented") +} + +func (t *Application) Types() hm.Types { return ((*Pair)(t)).Types() } + +func (t *Application) Eq(other hm.Type) bool { + if ot, ok := other.(*Application); ok { + return ot.A.Eq(t.A) && ot.B.Eq(t.B) + } + return false +} From 0327ec5372dc2636f0c236140a8c29a32914bb68 Mon Sep 17 00:00:00 2001 From: chewxy Date: Fri, 5 Jan 2018 01:47:20 +1100 Subject: [PATCH 10/10] Added a few methods and a couple of interfaces for Monuples and Pairs --- types/commonutils.go | 12 ++++++++++++ types/monuples.go | 6 ++++++ types/pairs.go | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/types/commonutils.go b/types/commonutils.go index 5c1e736..fd9a8d9 100644 --- a/types/commonutils.go +++ b/types/commonutils.go @@ -76,3 +76,15 @@ func (t Monuple) Normalize(k, v hm.TypeVarSet) (Monuple, error) { t.T = t2 return t, nil } + +// Pairer is any type that can be represented by a Pair +type Pairer interface { + hm.Type + AsPair() *Pair +} + +// Monupler is any type that can be represented by a Monuple +type Monupler interface { + hm.Type + AsMonuple() Monuple +} diff --git a/types/monuples.go b/types/monuples.go index 48bb9ae..2faded1 100644 --- a/types/monuples.go +++ b/types/monuples.go @@ -31,6 +31,8 @@ func (t Slice) Eq(other hm.Type) bool { return false } +func (t Slice) Monuple() Monuple { return Monuple(t) } + // Linear is a linear type (i.e types that can only appear once) type Linear Monuple @@ -56,6 +58,8 @@ func (t Linear) Eq(other hm.Type) bool { return false } +func (t Linear) Monuple() Monuple { return Monuple(t) } + // Ref is a reference type (think pointers) type Ref Monuple @@ -80,3 +84,5 @@ func (t Ref) Eq(other hm.Type) bool { } return false } + +func (t Ref) Monuple() Monuple { return Monuple(t) } diff --git a/types/pairs.go b/types/pairs.go index e7d7008..13c94f8 100644 --- a/types/pairs.go +++ b/types/pairs.go @@ -51,6 +51,10 @@ func (t *Choice) Eq(other hm.Type) bool { return false } +func (t *Choice) Clone() interface{} { return (*Choice)((*Pair)(t).Clone()) } + +func (t *Choice) Pair() *Pair { return (*Pair)(t) } + // Super is the inverse of Choice. It allows for supertyping functions. // // Supertyping is typically implemented as a adding an entry to the vtable/mangled table. @@ -78,6 +82,10 @@ func (t *Super) Eq(other hm.Type) bool { return false } +func (t *Super) Clone() interface{} { return (*Super)((*Pair)(t).Clone()) } + +func (t *Super) Pair() *Pair { return (*Pair)(t) } + // Application is the pre-unified type for a function application. // In a simple HM system this would not be needed as the type of an // application expression would be found during the unification phase of @@ -105,3 +113,7 @@ func (t *Application) Eq(other hm.Type) bool { } return false } + +func (t *Application) Clone() interface{} { return (*Application)((*Pair)(t).Clone()) } + +func (t *Application) Pair() *Pair { return (*Pair)(t) }