Created
January 2, 2025 15:56
-
-
Save ShalokShalom/f67bfb94c285f0219a7e451907a2ae35 to your computer and use it in GitHub Desktop.
Haxe (multiple dispatch)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.