Nim-like type inference or specialization

Hi I’m testing out the type inference in Haxe to see if I can get something similar to what I can do in Nim. What I’m trying to do is that I have one function, that can run on any type that has a x, y, vx, vy

This is as far as I got:

class Test {
  static function applyVelocity(p, dx = 1.0, dy = 1.0) {
    p.x += p.vx;
    p.y += p.vy;
    p.vy *= dy;
    p.vx *= dx;
  }
  
  static function main() {
    var t = new Thing();
    applyVelocity(a);
    
  }
}

class Thing {
	public var x: Float;
	public var y: Float;
	public var vx: Float;
	public var vy: Float;

	public function new() {}
}

Playground link:

Pretty much the applyVelocity(p) would be able to take any type and it’ll create a new applyVelocity for each type that can run the function. However it currently complains about one of the types not being an Int. Is this kind of thing possible in Haxe?

Here is the nim equivalent if anyone is interested:
https://play.nim-lang.org/#pasty=fnNlwSua

Some other ideas I tried in haxe:

  • Dynamic: Setting p to Dynamic makes it work as expected but I’m guessing it’s a lot slower since It is dynamic and not static.
  • Typedef + Generics: I got somewhat close with this but I couldn’t get x and y to work with setters and getters. Maybe I just need to figure out a way to use this one?

Despite syntactic similarities, you cannot treat properties and plain fields the same in Haxe’s type system.

The easiest thing to do is to use a macro I guess: Try Haxe!

1 Like

This looks like what I need, thanks!
(here is the code in the playground given by back2dos)

// use this for macros or other classes
class Macro {
	static public macro function applyVelocity(p, args:Array<haxe.macro.Expr>) {
    var dy = args.pop() ?? macro 1.0;
    var dx = args.pop() ?? macro 1.0;
		return macro {
			var __p__ = $p;
			__p__.x += __p__.vx;
			__p__.y += __p__.vy;
			__p__.vy *= $dy;
			__p__.vx *= $dx;
			__p__;
		}
	}
}

Looks like the same thing as nim where it generates the code at the call site.

You can do some casting stuff with typedef, I am still rather hazy on transients but they seem to optimise the output js significantly.

typedef TThing = {
	var x: Float;
  var y: Float;
  var vx: Float;
  var vy: Float;
}
@:structInit
@:transient  
class Thing {
  public var x: Float;
  public var y: Float;
  public var vx: Float;
  public var vy: Float;
	public inline function new ( x = 0., y=0., vx = 1., vy = 1. ) {
  	this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
	}
  public static inline function create(): Thing {
    return new Thing();
  }
}
@:structInit
@:transient  
class Thing2 {
  public var x: Float;
  public var y: Float;
  public var vx: Float;
  public var vy: Float;
	public inline function new ( x = 0., y=0., vx = 0., vy = 0. ) {
  	this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
	}
  public static inline function create(): Thing2 {
    return new Thing2();
  }
}
inline function applyVelocity(p: TThing, dx = 1.0, dy = 1.0) {
    p.x += p.vx;
    p.y += p.vy;
    p.vy *= dy;
    p.vx *= dx;
    return p;
}
inline function traceValues( p: TThing ){
    var x = p.x;
    var y = p.y;
    var vx = p.vx;
    var vy = p.vy;
		trace( ' x:$x, y:$y, vx:$vx, vy:$vy ' );
}
abstract Apple( Thing ){
  public static var name = 'Apple';
  public inline function new(){
		this = Thing.create();
  }
}
function main() {
  var apple = new Apple();
 	applyVelocity( ( cast apple: TThing ) );
  traceValues( ( cast apple: TThing ) );
  var notApple = new Thing2();
  applyVelocity( ( cast notApple: TThing ) );
  traceValues( ( cast notApple: TThing ) );
}