haxe
haxe copied to clipboard
Add a way to skip a `@:from` cast for consideration by top down inference.
In tink_core there's a whole bunch of abstracts over functions returning promises such as Next,Combiner and Recover where the @:from casts were never meant to be taken into account by top down inference, but rather the from (which happened to work with the somewhat borked behavior we had prior to 4.3). Now they're processed and this has some unintended side effects, ranging from additional allocations to compiler errors (although those are rare). So it'd be great if I could somehow have them skipped with some additional meta or whatever, really. Could also be @:from(ignoredByInference) or something of the sort.
I actually keep running into this quite a bit when using promises more directly.
It also occurred to me that in theory one may wish to opt out of considering from in inference, although this would be achievable by converting it to an "inference excluded" @:from cast.
I feel like I have to try pressing this issue once more, as I see it was labelled as Later.
Here's the how it affects tink_core reduced to a minimal(ish) example:
class Test {
static function main() {
getAnswer()
.next(v -> switch v {
case 42: 'Perfect!';
case 40, 41, 43, 44: 'Close enough!';
default: new Error();
})
.handle(o -> trace(Std.string(o)));
}
static function getAnswer():Promise<Int>
return Std.random(6) + 39;
}
class Error { public function new() {} }
abstract Promise<T>((handler:(result:Outcome<T, Error>)->Void)->Void) {
inline function new(f) this = f;
public function next<X>(transform:Next<T, X>):Promise<X>
return new Promise(
handler -> this(o -> switch o {
case Success(v): transform(v).handle(handler);
case Failure(e): handler(Failure(e));
})
);
@:from static function ofOutcome<T>(o:Outcome<T, Error>):Promise<T>
return new Promise<T>(h -> h(o));
@:from static function ofValue<T>(v:T):Promise<T>
return ofOutcome(Success(v));
@:from static function ofError<T>(e:Error):Promise<T>
return ofOutcome(Failure(e));
public function handle(cb)
this(cb);
}
@:callable
abstract Next<In, Out>(In->Promise<Out>) from In->Promise<Out> {
// @:from static function ofSync<In, Out>(f:In->Out):Next<In, Out>
// return v -> (f(v):Promise<Out>);
}
enum Outcome<T, E> {
Success(data:T);
Failure(error:E);
}
With the implicit cast uncommented, it doesn't compile on nightly anymore, as opposed to 4.2.5. The demonstrated usage of next, i.e. return a value or an error - or yet another promise - and let the compiler figure it out is pretty common and somewhat modeled after how then works in ECMAScript promises (except that it does so by introspecting the return value's type at runtime). I definitely do not want to break this usage, but then again I don't have to.
What I can do is remove all the implicit casts from Next (in the actual lib there are a few more), as I suspect they're quite rarely used and it's easy enough to convert promise.next(synchronousTransform) to promise.next(o -> synchronousTransform(o)). But if it can be helped, I'd like to avoid having to make a breaking change to support Haxe 4.3 and the easiest way I see would be to allow having them ignored as before.
Beyond compatibility, I don't particularly care for being able to control inference thusly, so I suppose that if it can't be handled in 4.3, we might as well close this.
This should actually be quite easy to implement, but I don't know what's the best way to tag these functions.
Personally I would suggest parametrizing @:from (and potentially @:to) with various options, to avoid introducing more meta tags. We've already discussed restricting transitivity to specific casts. I'm just inventing things, but we could for example also have things like @:from(priority=123), or @:to(withOperators) (which would make it so that the operators from the target type are made available to the source type) or whatever. I realize that this cuts awfully close to typed meta, but I guess we could avoid getting into that subject just yet?
I'm somewhat worried about the discoverability of such arguments, but I agree it makes sense to store this kind of information in the arguments of @:from because that's what it's related to. I'll look into implementing @:from(ignoriedByInference) like you suggested, and try to design it in a way that other things can be added later.
This implementation is rather scuffed, but it should do for now. Long-term, we can support these kinds of meta arguments in meta.json and generate the appropriate handling code. This should be possible without discussing the forbidden topic.
Just checked and it works perfectly for me. Dunno if you wanna keep this open to remember that meta.json idea, but as far as I'm concerned, everything is awesome and this can be closed. Either way, thanks a lot! ;)
Glad to hear, and yes that's why I'm keeping it open.