COMMUNITY

:genericBuild to implement usable type parameters akin to C++ or D

The way I understand :genericBuild, it should be possible to implement something like C++/D templates (parametric polymorphism/duck typing specifically). I attempted it, but it’s not trivial so I was wondering if there is any existing library or code snippet that does this, because I don’t want to reinvent the wheel.

Illustration of what I want:

@:generic
@:genericBuild(Macro.build())
class X<T> {
  public function foo(arg:T) {
    return arg.bar();
  }
}

and X would be instantiatable with any type parameter that has a bar member of type Void->?

I would love any discussion on this. Macros are great, but I feel like C++/D templates are much more intuitive, and having an option for at least a primitive version of them would be great.

EDIT: The build macro would just run through the type definition and replace all occurences of T with the actual instantiation parameter.

in tink_lang you can @:forward function calls to to an instance variable. check the docs

That’s pretty cool, but it doesn’t bypass (logical) type erasure, which is what I’m looking for.

Type erasure is where the runtime representation is less rich than the compile time representation, am I right?

Yeah, I guess

So what’s being erased that you need in the target source? I mean, you can already use:

  abstract X<T>(T) from T to T{
      public function new(self:T) this = self;
      public function foo(){
        this.bar();
      }
  }

That does not compile. this is of type T, which is erased and therefore has no member bar.

Now I get it.
If there’s nothing provable about T I don’t see in what way it is being erased.

abstract X<T: { bar : Void->Void } >(T) from T to T{
      public function new(self:T) this = self;
      public function foo(){
        this.bar();
      }
  }

As I mentioned .bar() is supposed to be Void->?, not Void->Void. .bar() of T can return anything it wants, which then also becomes the return type of X.foo(). There is a wide variety of problems that generics using type erasure cannot accomplish. I’m not really here to discuss that.

EDIT: I realize X could be further parametrized with U and bar made into Void->U. But that approach is not really scalable. And I have another problem with it that I will post shortly.

It’s possible to encode Higher Types via phantom types, so what you’re asking is possible, a case would come in handy.

class Modifier<T> {
    var member:T;

    public function new() {}

    public function apply(to:T) {
        this.member = to;
        // Assume that T is also Modifiable<T>
        // how do we add this to to.modifiers?
    }
}

class AFooModifier extends Modifier<Foo> {
    public function doSomethingWithFooMember() {
        // Do something with Foo member
    }
}

interface Modifiable<T> {
    function addModifier(modifier:Modifier<T>):Void;
}

class Foo implements Modifiable<Foo> {
    public function new() {}

    public final modifiers = new Array<Modifier<Foo>>();

    public function addModifier(modifier:Modifier<Foo>):Void {
        this.modifiers.push(modifier);
    }
}

How would we constraint T to be Modifiable?

hmm

It’s tying the knot, It’s mad problematic.

How would we constraint T to be Modifiable?

It really looks as though you just want to constrain the param, in which case this will do:

class Modifier<T:Modifiable> {
    var member:T;
}

Further reading: Constraints - Haxe - The Cross-platform Toolkit


As for the original problem, you can use tink_lang’s partial implementations:

@:tink interface X<T> {
  function foo(arg:T) {
    return arg.bar();
  }
}

class Y implements X<SomethingWithBar> {
  // foo is automatically generated
}

You can also get it to work with @:genericBuild, with the restriction that the type itself (X in this case) is not usable anymore, because all references to it are replaced.

Wait, you can do that? lol. If Modifiable has it’s own type parameter?

No, you can’t. That’s precisely the problem. EDIT: Specifically that Modifiable then also uses Modifier, which requires an infinite regression of Modifiable parameters.

Also it’s not possible to use arg.bar() just because it’s in a partial implementation.

Subclassing Modifier is impossible because why?

Recursive types are supported also, so again, I just gotta get it in my thick head.

It’s not impossible, but much more cumbersome and doesn’t scale as well in terms of code size as the number of Modifiables goes up. If the type system could potentially do something for me and prevent me from repeating myself, it should do that.

Not got it down pat, but a ModifiableModifier with a type constraint?

If you’re not putting in the recursive type on the ground floor, you’ll always hit the terminal in any case.

class ModifiableModifier<T:Modifiable<T>> extends Modifier<T>{}

class AFooModifier extends ModifiableModifier<Foo> {
	public function doSomethingWithFooMember() {
			// Do something with Foo member
			for( modifier in this.member.modifiers ){
				modifier.addModifier
			}
	}
}

Oh, I see. You’re right, that does work.

I’m still interested in answers to the original question though.

It’s not gonna map 100%. Not being a c++ person, I couldn’t say exactly where you’re gonna feel the pain. Haxe’s type system is pretty healthy, imo.