haxe icon indicating copy to clipboard operation
haxe copied to clipboard

EitherType in function types

Open andyli opened this issue 6 years ago • 11 comments

class Test {
    static function test(f:haxe.extern.EitherType<Int,String>->Void) {
        f(123);
        f("abc");
    }
    static function main() {
        test(function(v:Int) {
            trace(Std.is(v, Int)); //should be always true, but actually print true then false
        });
    }
}

I think the test() call shouldn't type-check. i.e. Int->Void shouldn't unifies with haxe.extern.EitherType<Int,String>->Void.

andyli avatar Apr 15 '19 18:04 andyli

Unless I am missing something, I think it's expected since haxe.extern.EitherType is meant for externs likely doing their own runtime checks, so it's up to your test() function to call f appropriately.

kLabz avatar Apr 16 '19 06:04 kLabz

I don't think type soundness has anything to do with designing for extern usage or not.

It is simply unsound for A->Void to unify with EitherType<A,B>->Void. It's similar to the case as follows:

var f:Float->Void = function(n:Int) {}; // can't compile,  n : Int -> Void should be Float -> Void

If I want something that can accept both A->Void and B->Void, I can write EitherType<A->Void, B->Void>.

andyli avatar Apr 16 '19 07:04 andyli

I would have said it's more similar to var f:Int->Void = function(i:Float) {}; (or var f:B->Void = function(a:A) {}; with B extends A), which compiles.

Either way I now see your point, but it does not seem to me to be possible to handle this specific case without breaking many things if the above examples are indeed the same thing.

kLabz avatar Apr 16 '19 07:04 kLabz

more similar to var f:Int->Void = function(i:Float) {}; (or var f:B->Void = function(a:A) {}; with B extends A)

No, they are not similar, but exactly the opposite. Function types are contravariant, which means A->Void can unify with B->Void iff B unifies with A, but not the opposite.

var f:Float->Void = function(n:Int) {}; //can't compile, and it's correct for the compiler not to compile
var f:Int->Void = function(f:Float) {}; //compiles, and it's correct too

andyli avatar Apr 16 '19 07:04 andyli

I agree that this violates variance because the EitherType is the more general type.

Simn avatar Apr 16 '19 07:04 Simn

Seems like I understood it backwards, sorry about that :confused:

kLabz avatar Apr 16 '19 07:04 kLabz

If I apologized every time I got tripped by function argument variance you'd think I was Canadian.

Simn avatar Apr 16 '19 07:04 Simn

The underlying problem here is that EitherType has to T1 to T2 which makes it unify in this direction. I'm not sure if we could just change that...

Simn avatar Jul 01 '19 11:07 Simn

Shouldn't we check that the argument of the closure is compatible with every to of the abstract?

RealyUniqueName avatar Jul 01 '19 17:07 RealyUniqueName

Is that even possible? If that was the case then we wouldn't need the abstract in the first place...

Simn avatar Jul 01 '19 17:07 Simn

Probably possible in some rare situations with similar abstract. E.g. Either3<T1,T2,T3>

RealyUniqueName avatar Jul 01 '19 18:07 RealyUniqueName