Skip to content

Instantly share code, notes, and snippets.

@ShalokShalom
Created January 2, 2025 15:56
Show Gist options
  • Save ShalokShalom/f67bfb94c285f0219a7e451907a2ae35 to your computer and use it in GitHub Desktop.
Save ShalokShalom/f67bfb94c285f0219a7e451907a2ae35 to your computer and use it in GitHub Desktop.
Haxe (multiple dispatch)
// Type definitions for method signatures and dispatch information
private typedef MethodSignature = {
var paramTypes:Array<Class<Dynamic>>;
var method:Dynamic;
}
// Dispatcher implementation that could be used by a transpiler
class GenericDispatcher {
// Static registry for all multi-methods
private static var methodRegistry:Map<String, Array<MethodSignature>> = new Map();
// Unique identifier generator for method names
private static var methodCounter:Int = 0;
/**
* Registers a multi-method with variable arity
* @param methodName Unique identifier for the method group
* @param paramTypes Array of parameter types
* @param method The actual method implementation
* @return String The unique identifier for this specific implementation
*/
public static function registerMethod(
methodName:String,
paramTypes:Array<Class<Dynamic>>,
method:Dynamic
):String {
if (!methodRegistry.exists(methodName)) {
methodRegistry.set(methodName, []);
}
var signature:MethodSignature = {
paramTypes: paramTypes,
method: method
};
methodRegistry.get(methodName).push(signature);
return '${methodName}_${methodCounter++}';
}
/**
* Dispatches a call to the most specific matching method
* @param methodName The method group identifier
* @param args Array of arguments to pass to the method
* @return Dynamic The result of the method call
*/
public static function dispatch(methodName:String, args:Array<Dynamic>):Dynamic {
var methods = methodRegistry.get(methodName);
if (methods == null) {
throw 'No methods registered for ${methodName}';
}
var matchingMethod = findMostSpecificMethod(methods, args);
if (matchingMethod == null) {
throw 'No matching method found for ${methodName} with given arguments';
}
return Reflect.callMethod(null, matchingMethod.method, args);
}
/**
* Finds the most specific matching method for given arguments
* @param methods Array of method signatures to search through
* @param args Array of arguments to match against
* @return MethodSignature The most specific matching method signature
*/
private static function findMostSpecificMethod(
methods:Array<MethodSignature>,
args:Array<Dynamic>
):MethodSignature {
var validMethods = methods.filter(function(sig) {
if (sig.paramTypes.length != args.length) return false;
for (i in 0...args.length) {
var argType = Type.getClass(args[i]);
if (argType == null || !isCompatibleType(argType, sig.paramTypes[i])) {
return false;
}
}
return true;
});
if (validMethods.length == 0) return null;
// Sort by specificity (most specific first)
validMethods.sort((a, b) -> calculateSpecificity(b) - calculateSpecificity(a));
return validMethods[0];
}
/**
* Checks if a type is compatible with a target type
* @param sourceType The type to check
* @param targetType The type to check against
* @return Bool Whether the types are compatible
*/
private static function isCompatibleType(sourceType:Class<Dynamic>, targetType:Class<Dynamic>):Bool {
if (sourceType == targetType) return true;
var currentType = sourceType;
while (currentType != null) {
if (currentType == targetType) return true;
currentType = Type.getSuperClass(currentType);
}
return false;
}
/**
* Calculates the specificity score of a method signature
* Higher scores mean more specific parameter types
*/
private static function calculateSpecificity(signature:MethodSignature):Int {
var score = 0;
for (type in signature.paramTypes) {
var depth = 0;
var currentType = type;
while (currentType != null) {
depth++;
currentType = Type.getSuperClass(currentType);
}
score += depth;
}
return score;
}
}
// Macro for generating dispatch code (example of how it could be used)
#if macro
import haxe.macro.Expr;
import haxe.macro.Context;
class DispatchMacro {
public static macro function makeMultiMethod(name:String, expr:Expr):Expr {
// This would generate the appropriate dispatch registration code
// at compile time
return macro {
GenericDispatcher.registerMethod($v{name},
[/* type information would be extracted here */],
$expr);
}
}
}
#end
@ShalokShalom
Copy link
Author

ShalokShalom commented Jan 2, 2025

I'll create a simple example comparing multiple dispatch across three languages: Haxe, Julia and Clojure.
Both Julia and Clojure use multiple dispatch a lot.

Let's implement a simple geometric area calculation system, that handles different shape combinations.

# Julia
abstract type Shape end

struct Circle <: Shape
    radius::Float64
end

struct Rectangle <: Shape
    width::Float64
    height::Float64
end

# Methods are automatically dispatched based on argument types
function overlap(a::Circle, b::Circle)
    "Circle-Circle overlap"
end

function overlap(a::Circle, b::Rectangle)
    "Circle-Rectangle overlap"
end

function overlap(a::Rectangle, b::Circle)
    "Rectangle-Circle overlap"
end

function overlap(a::Rectangle, b::Rectangle)
    "Rectangle-Rectangle overlap"
end

# Usage
c1 = Circle(5.0)
c2 = Circle(3.0)
r1 = Rectangle(4.0, 6.0)

println(overlap(c1, c2))      # Circle-Circle overlap
println(overlap(c1, r1))      # Circle-Rectangle overlap
println(overlap(r1, c1))      # Rectangle-Circle overlap
// This implementation
class Shape { }
class Circle extends Shape {
    public var radius:Float;
    public function new(radius:Float) {
        this.radius = radius;
    }
}
class Rectangle extends Shape {
    public var width:Float;
    public var height:Float;
    public function new(width:Float, height:Float) {
        this.width = width;
        this.height = height;
    }
}

class Main {
    static function main() {
        // Register all method combinations
        GenericDispatcher.registerMethod(
            "overlap",
            [Circle, Circle],
            function(c1:Circle, c2:Circle) return "Circle-Circle overlap"
        );

        GenericDispatcher.registerMethod(
            "overlap",
            [Circle, Rectangle],
            function(c:Circle, r:Rectangle) return "Circle-Rectangle overlap"
        );

        GenericDispatcher.registerMethod(
            "overlap",
            [Rectangle, Circle],
            function(r:Rectangle, c:Circle) return "Rectangle-Circle overlap"
        );

        var c1 = new Circle(5);
        var c2 = new Circle(3);
        var r1 = new Rectangle(4, 6);

        trace(GenericDispatcher.dispatch("overlap", [c1, c2]));
        trace(GenericDispatcher.dispatch("overlap", [c1, r1]));
        trace(GenericDispatcher.dispatch("overlap", [r1, c1]));
    }
}
;; Clojure
(defmulti overlap (fn [shape1 shape2] 
    [(class shape1) (class shape2)]))

(defrecord Circle [radius])
(defrecord Rectangle [width height])

(defmethod overlap [Circle Circle]
  [c1 c2]
  "Circle-Circle overlap")

(defmethod overlap [Circle Rectangle]
  [c r]
  "Circle-Rectangle overlap")

(defmethod overlap [Rectangle Circle]
  [r c]
  "Rectangle-Circle overlap")

(defmethod overlap [Rectangle Rectangle]
  [r1 r2]
  "Rectangle-Rectangle overlap")

;; Usage
(let [c1 (->Circle 5)
      c2 (->Circle 3)
      r1 (->Rectangle 4 6)]
  (println (overlap c1 c2))    ; Circle-Circle overlap
  (println (overlap c1 r1))    ; Circle-Rectangle overlap
  (println (overlap r1 c1)))   ; Rectangle-Circle overlap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment