Array with values of type X or any other, which inherits from it

I want to have an array of elements which are of type ActionOptions or any other type which inherits from it.

This snippet will fail because arr is strictly typed to contain only ActionOptions elements. But I can’t think of any way to do it differently :frowning:

I could use Array<Dynamic> instead, but I still want to have compile-time warning about some unexpected values.

class Sandbox {
  static function main(){
    var arr:Array<ActionOptions> = [];
    arr.push({start: 0, duration: 1});
    arr.push({start: 2, duration: 4, test: 1});
  }
}

typedef ActionOptions = {
  var start:Float;
  var duration:Float;
}
typedef SecondActionOptions = {
  > ActionOptions,
  var test:Int;
}

Generally for my game I want to have an array of “descriptors”, which at a given point will tell which action to instantiate using what parameters.

You could use an abstract to augment the push function. Also you will need to make sure the compiler sees the start and duration field as floats, so it can pass the constraint check.

class Test {
  static function main(){
    var arr:ActionOptionsArray = [];
    arr.push({start: 0., duration: 1.});
    arr.push({start: 2., duration: 4., test: 1});
  }
}
@:forward
abstract ActionOptionsArray(Array<ActionOptions>) from Array<ActionOptions> to Array<ActionOptions>{
    public inline function push<T:ActionOptions>(v:T)
        return this.push(v);
}

typedef ActionOptions = {
  var start:Float;
  var duration:Float;
}
typedef SecondActionOptions = {
  > ActionOptions,
  var test:Int;
}

I think it’s much simpler to just have an optional value in the typedef.

But it depends greatly on your needs, you can also use short form for the typedef with ‘?’ when comma separated but it’s perhaps clearer this way.

Just to clarify. The method proposed by Justin is definitely preferable, but is limited: you have to know all the optional fields ahead of time and define them accordingly. As opposed to that, Kevin’s solution allows arbitrary extra fields, for the cost of relying on abstracts and constrained type parameters.

Alternatively, you can use classes:

class Test {
    static public function main() {
        var arr : Array<ActionOptions> = [];
        arr.push(new ActionOptions(0, 1));
        arr.push(new SecondActionOptions(2, 4, 1));
    }
}

class ActionOptions {
    public function new(start : Float, duration : Float) {
        this.start = start;
        this.duration = duration;
    }
    public var start(default, null) : Float;
    public var duration(default, null) : Float;
}
class SecondActionOptions extends ActionOptions {
    public function new(start : Float, duration : Float, test : Int) {
        super(start, duration);
        this.test = test;
    }
    public var test(default, null) : Int;
}

It actually seems that the Haxe type system does not see structure extensions as sub-types the way sub-classes are seen.