Skip to content
This repository has been archived by the owner on Nov 6, 2024. It is now read-only.

Commit

Permalink
Tweak unification algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
gdotdesign committed Jan 12, 2023
1 parent caed97d commit f6e725b
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 30 deletions.
1 change: 1 addition & 0 deletions spec/integration_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ describe HM do

expect_normalized("Function(value: a, context: Test.Context(b), Test.Context(c))", "Function(value: a, context: Test.Context(b), Test.Context(c))")

expect_not_unify("Function(Maybe(value), Maybe(value))", "Function(value: value, Maybe(value))")
expect_not_unify("Function(String,Number)", "Function(Number,Number)")
expect_not_unify("Array(x)", "Array(x,y)")
expect_not_unify("Array(x,y)", "Array(x)")
Expand Down
10 changes: 10 additions & 0 deletions src/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ module HM
def empty?
true
end

# Returns whether this type includes (equals) the other type or variable.
def includes?(other : Checkable)
self == other
end
end

# Represents a type which has a name and can have many fields.
Expand Down Expand Up @@ -80,6 +85,11 @@ module HM
def empty?
fields.empty?
end

# Returns whether this type includes the other type or variable.
def includes?(other : Checkable)
self == other || fields.any? { |field| field.item.includes?(other) }
end
end

# Represents a definition of a type.
Expand Down
44 changes: 14 additions & 30 deletions src/unifier.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,33 @@ module HM
mapping =
{} of Variable => Checkable

result =
unify(normalize(a), normalize(b), mapping)

substitue(result, mapping) if result
unify(normalize(a), normalize(b), mapping).try do |result|
substitue(result, mapping) if result
end
end

# This method tries to unify the given types by traversing it's parameters
# and producing a mapping of variables to types (Variable => Checkable) which
# later on we can use to substitue to one of the types and create a unified
# type.
# and producing a mapping of variables to types (Variable => Checkable)
# which later on we can use to substitue to one of the types and create a
# unified type.
#
# It is important that this method assumes that both of the types were
# normalized before, since normalization orders the fields, so as an
# optimization we don't normalize the types here.
private def unify(a : Checkable, b : Checkable, mapping : Hash(Variable, Checkable))
case {a, b}
in {Variable, Variable}
# If there are two variables we need to check if they already have
# assigned types, if they do we need to test if they are equal, if not
# we cannot unify, otherwise we can just point them to each other
# (depending if they have instances or not).
instanceA = mapping[a]?
instanceB = mapping[b]?

if instanceA && instanceB
unify(instanceA, instanceB)
elsif instanceA && instanceA != b
mapping[b] = a
# If the mapped type contains the varaible it would result in a circular
# substitution so it's not unifiable.
return nil if mapping[b]?.try(&.includes?(a))

if mapping[a]?
unify(b, a)
else
mapping[a] = b
a.tap { mapping[a] = b }
end
in {Variable, Type}
# If we have a variable and a type we need to check if the variable
# already points to the same type, if not, we cannot unify, otherwise
# we can just point the variable to the type.
instance = mapping[a]?

# This check is important since the variables can point to each other.
if instance && instance.is_a?(Type)
unify(instance, b)
else
mapping[a] = b
end
a.tap { mapping[a] = b }
in {Type, Variable}
# In this branch we just call unify for the case above.
unify(b, a, mapping)
Expand Down

0 comments on commit f6e725b

Please sign in to comment.