HaxeFoundation / haxe

Haxe - The Cross-Platform Toolkit
https://haxe.org
6.18k stars 655 forks source link

Multitype abstract redesign proposal #8743

Closed Ilir-Liburn closed 5 years ago

Ilir-Liburn commented 5 years ago

Somewhere I read @Simn wants to redesign multitype abstract, but he is not sure how. Here is my proposal

abstract MyMap<K, V>(IMap<K, V>) {
    public function new();
    @:from inline function fromInt<K:Int>() this = new IntMap<V>();
    @:from inline function fromString<K:String>() this = new StringMap<V>();
    @:to function toIntMap():IntMap<V> return cast this;
    @:to function toStringMap():StringMap<V> return cast this;
    @:to function toInterface():IMap<K, V> return this;
}

Idea is to introduce non-static @:from function and parameter constraint to select implementation where 'this' is used like in a new().If constructor defines arguments, each non-static @:from have to match.

Ilir-Liburn commented 5 years ago

Here is a simulation to demonstrate possibilities

import haxe.Constraints.IMap;
import haxe.ds.StringMap;
import haxe.ds.IntMap;

abstract MyMap<K, V>(IMap<K, V>) {
    public function new() { var v:K = null; this = Std.is(v, Int) ? cast new IntMap<V>() : cast new StringMap<V>(); }
//    @:from inline function fromInt<K:Int>() this = new IntMap<V>();
//    @:from inline function fromString<K:String>() this = new StringMap<V>();
    @:to function toIntMap():IntMap<V> return cast this;
    @:to function toStringMap():StringMap<V> return cast this;
    @:to function toInterface():IMap<K, V> return this;
    @:to static inline function toConverted<K, V>(i:IMap<K, V>):MyMap<V, K> {
        var r = new MyMap<V, K>();
        for (key => value in i) r[value] = key;
        return r;
    }
    @:arrayAccess @:noCompletion public inline function arrayWrite(k:K, v:V):V {
        this.set(k, v);
        return v;
    }

    public static function test() {
        var m1 = new MyMap<Int, String>();
        m1[0] = "zero";
        trace(m1);
        var m2 = new MyMap<String, Int>();
        m2["zero"] = 0;
        trace(m2);
        m1 = m2; // convert StringMap to IntMap
        trace(m1);
        var i1:IMap<Int, String> = m1;
//        var i1e:IMap<Int, String> = m2; // MyMap<String, Int> should be haxe.IMap<Int, String>
        var i2:IMap<String, Int> = m2;
//        var i2e:IMap<String, Int> = m1; // MyMap<Int, String> should be haxe.IMap<String, Int>
        var im1:IntMap<String> = m1;
//        var im1e:IntMap<String> = m2; // MyMap<String, Int> should be haxe.ds.IntMap<String>
        var sm1:StringMap<Int> = m2;
//        var sm1e:StringMap<Int> = m1; // MyMap<Int, String> should be haxe.ds.StringMap<Int>
    }
}
Simn commented 5 years ago

This has the exact same problems the current implementation has: The abstract still has to know all of its specializations, and inline functions cause the abstract type to disappear.

We need a completely different approach here. I have a vague idea, but will have to see if I can turn that into a POC.

Ilir-Liburn commented 5 years ago

@Simn, could you please elaborate? What do you mean by: has to know all of its specializations. Isn't that a purpose of multitype? And how inline functions cause abstract to disappear?

Ilir-Liburn commented 5 years ago

@Simn, you probably want multitype to be able to recognize each class implementing interface without specifying each specialization? That would be really advanced. For my second question, I have to think about what it could mean.