Skip to content

Commit

Permalink
Merge 71799ab into 61efb32
Browse files Browse the repository at this point in the history
  • Loading branch information
chewxy committed Dec 28, 2017
2 parents 61efb32 + 71799ab commit 7f305d5
Show file tree
Hide file tree
Showing 14 changed files with 636 additions and 9 deletions.
7 changes: 7 additions & 0 deletions substitutions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions typeVariable.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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", rune(t))
return
}
fmt.Fprintf(s, "<%d>", rune(t))
}
func (t TypeVariable) Eq(other Type) bool { return other == t }
78 changes: 78 additions & 0 deletions types/commonutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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.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 {
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)
}

// 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()) }

// 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 {
T hm.Type
}

// Apply applies a substitution to the monuple type.
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.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 Monuple{}, err
}
t.T = t2
return t, nil
}
105 changes: 105 additions & 0 deletions types/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package hmtypes

import (
"fmt"

"github.com/chewxy/hm"
)

// Function is a type constructor that builds function types.
type Function Pair

// NewFunction creates a new FunctionType. Functions are by default right associative. This:
// NewFunction(a, a, a)
// is short hand for this:
// NewFunction(a, NewFunction(a, a))
func NewFunction(ts ...hm.Type) *Function {
if len(ts) < 2 {
panic("Expected at least 2 input types")
}

retVal := borrowFn()
retVal.A = ts[0]

if len(ts) > 2 {
retVal.B = NewFunction(ts[1:]...)
} else {
retVal.B = ts[1]
}
return retVal
}

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 {
return nil, err
}

if b, err = t.B.Normalize(k, v); err != nil {
return nil, err
}

return NewFunction(a, b), nil
}
func (t *Function) Types() hm.Types { return ((*Pair)(t)).Types() }

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
}

// Other methods (accessors mainly)

// Arg returns the type of the function argument
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 *Function) Ret(recursive bool) hm.Type {
if !recursive {
return t.B
}

if fnt, ok := t.B.(*Function); 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 *Function) FlatTypes() hm.Types {
retVal := hm.BorrowTypes(8) // start with 8. Can always grow
retVal = retVal[:0]

if a, ok := t.A.(*Function); ok {
ft := a.FlatTypes()
retVal = append(retVal, ft...)
hm.ReturnTypes(ft)
} else {
retVal = append(retVal, t.A)
}

if b, ok := t.B.(*Function); ok {
ft := b.FlatTypes()
retVal = append(retVal, ft...)
hm.ReturnTypes(ft)
} else {
retVal = append(retVal, t.B)
}
return retVal
}

// Clone implenents cloner
func (t *Function) Clone() interface{} {
p := (*Pair)(t)
cloned := p.Clone()
return (*Function)(cloned)
}
99 changes: 99 additions & 0 deletions types/function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package hmtypes

import (
"testing"

"github.com/chewxy/hm"
"github.com/stretchr/testify/assert"
)

func TestFunctionTypeBasics(t *testing.T) {
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())
}

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(NewFunction(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 = NewFunction(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).(*Function)
if !fn.Eq(fas.expected) {
t.Errorf("Expected %v. Got %v instead", fas.expected, fn)
}
}

// bad shit
f := func() {
NewFunction(hm.TypeVariable('a'))
}
assert.Panics(t, f)
}

var fnApplyTests = []struct {
fn *Function
sub hm.Subs

expected *Function
}{
{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)
{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
{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 := 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 := 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 := NewFunction(hm.TypeVariable('a'), hm.TypeVariable('b'), hm.TypeVariable('c'))
assert.Equal(t, fnType.Clone(), fnType)

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)
}
5 changes: 5 additions & 0 deletions types/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hmtypes

type Cloner interface {
Clone() interface{}
}
Loading

0 comments on commit 7f305d5

Please sign in to comment.