Skip to content

Instantly share code, notes, and snippets.

@unworthyEnzyme
Last active January 2, 2025 09:22
Show Gist options
  • Save unworthyEnzyme/22538bb8083682b0c8d383337e82f334 to your computer and use it in GitHub Desktop.
Save unworthyEnzyme/22538bb8083682b0c8d383337e82f334 to your computer and use it in GitHub Desktop.
Multi-methods/multiple dispatch implemented in ruby
class Type
attr_accessor :name
attr_accessor :supertype
def initialize(name, supertype = nil)
@name = name
@supertype = supertype
end
def is?(type)
return true if self == type
return false if @supertype.nil?
@supertype.is?(type)
end
def distance(type)
# We only want to consider the chain not the tree so we don't consider types from different branches. e.g. Real and String
raise "Not a subtype" unless is?(type)
# If we are at the same type, we are 0 distance away
return 0 if self == type
# We are whatever distance `type` away from our supertype + 1.
# Example:
# real.distance(any) = 1 + complex.distance(any)
# = 1 + 1 + number.distance(any)
# = 1 + 1 + 1 + any.distance(any)
# = 1 + 1 + 1 + 0
# = 3
1 + @supertype.distance(type)
end
def to_s
@name
end
def ==(other)
@name == other.name
end
end
class Signature
attr_accessor :types
def initialize(types)
@types = types
end
def conforms?(signature)
# for a signature to conform to this one:
# 1. it must have the same number of types
return false if signature.types.length != @types.length
# 2. each type must be a subtype of the corresponding type in this signature
@types.zip(signature.types).all? { |a, b| a.is?(b) }
end
def distance(signature)
# the distance between two signatures is the sum of the distances between the types
@types.zip(signature.types).sum { |a, b| a.distance(b) }
end
def to_s
"(#{@types.map(&:to_s).join(", ")})"
end
def ==(other)
@types == other.types
end
end
class FunctionTable
attr_accessor :functions
def initialize()
@functions = []
end
def add(function)
raise "Function already exists" if @functions.include?(function)
@functions << function
end
def find(function)
# find all the signatures that conform to the given signature
candidates = @functions.select { |m| function.signature.conforms?(m.signature) && m.name == function.name }
# sort them by distance from closest to furthest.
sorted_by_distance = candidates.sort_by { |m| function.signature.distance(m.signature) }
# find the closest one.
# There may be more than one with the same distance, so we find all of them.
distances = sorted_by_distance.map { |m| function.signature.distance(m.signature) }
min_distance = distances.min
closest_functions = sorted_by_distance.select { |m| function.signature.distance(m.signature) == min_distance }
raise "Ambiguous function call between #{closest_functions}" if closest_functions.length > 1
closest_functions.first
end
end
class Function
attr_accessor :name, :signature
def initialize(name, signature)
@name = name
@signature = signature
end
def to_s
"#{@name}#{@signature}"
end
def ==(other)
@name == other.name && @signature == other.signature
end
end
any = Type.new("Any")
number = Type.new("Number", any)
complex = Type.new("Complex", number)
real = Type.new("Real", complex)
f1 = Function.new("f", Signature.new([number, number]))
f2 = Function.new("f", Signature.new([complex, complex]))
f3 = Function.new("f", Signature.new([real, real]))
table = FunctionTable.new
table.add(f1)
table.add(f2)
table.add(f3)
call_signature = Signature.new([real, real])
puts table.find(Function.new("f", call_signature)) == f3 # true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment