-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Open Discussion] Cast Specification #120
Comments
Why introduce a keyword for |
What about using the '?' operator for safe case, like safe access:
v:T = cast expr // Throws
v:T = cast? expr // returns null
…On Sat, Mar 30, 2024 at 10:02 PM Aurel ***@***.***> wrote:
Why introduce a keyword for downcast instead of adding a Std.downcast? Is
it to avoid spelling out the type in the case that the variable is already
declared of type Null<T>? (I think your last example should say Null<T>
for null safety.)
—
Reply to this email directly, view it on GitHub
<#120 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMWTVRVN3VPV5OZPJNA573Y22ZVHAVCNFSM6AAAAABFLGWPKSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRYGA3TSMRZGE>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
This not actually true. In safe casts nothing is ensured at compile time. This compiles without issues: |
Hmm. I think I can somehow understand the basic motivation, although it would help to understand what exactly the problem is you're trying to solve and weigh that against the impact of making such changes. I see some issues:
While we're on the subject: EDIT: To expand on that thought. I think it would be nice to be able to convert any value to any type in a runtime checked fashion, without the overhead of try/catch and exceptions and what not. Right now, that is as I said Given the overhead of exceptions, I'm having trouble to comprehend why the type conversion that produces them is pushed to become even more ubiquitous. Not that branching on type is a particularly advisable approach, but when it's done, it's typically for a good reason and it should be as cheap as it can possibly be, and as concise. I.e. the diametrical opposite of this: try {
var x = cast(y, X);
x.doSomethingX();
}
catch (e:Dynamic) {
someDefaultStuff();
} That can't be good. On a slightly related note, Java (which despite recent developments still makes for a relatively conservative point of reference) now has these: public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (shape instanceof Circle c) {
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
return switch (shape) {
case Rectangle r -> 2 * r.length() + 2 * r.width();
case Circle c -> 2 * c.radius() * Math.PI;
default -> throw new IllegalArgumentException("Unrecognized shape");
};
} Possible syntax for this: public static function getPerimeter(shape:Shape) {
return
if (shape is var r:Rectangle) 2 * r.length() + 2 * r.width();
else if (shape is var c:Circle) 2 * c.radius() * Math.PI;
else throw "Unrecognized shape";
}
}
public static function getPerimeter(Shape shape) {
return switch shape {
case (r:Rectangle): 2 * r.length() + 2 * r.width();
case (c:Circle): 2 * c.radius() * Math.PI;
default: throw "Unrecognized shape";
};
} I do kinda like the idea that |
I agree the is/cast needs rework, hence my post to open up other possibilities. var r : Any;
if( r is Array<String> ) return r[0].length;
if( r is String && r.length > 0 ) return -1; That would translate to: if( r is Array<String> ) { var r' : Array<String> = cast r; return r'[0].length; }
if( r is String ) { var r' : String = cast r; if( r'.length > 0 ) return -1; } And some tricky cases: if( r is Bool ) r = 0; // assign should still work
if( r is Int ) r = r > 0 ? "" : null;
// should error as you cannot both use the retyped variable and assign it with its new type ? |
I don't really like that. It only works on variables and adds the tricky cases you described. And one can easily think up even worse ones: var r : Any;
function trololo() {
r = 42;
}
if( r is Array<String>) { trololo(); return r[0].length }; // ohnoooo! I want to evaluate an arbitrary expression, runtime type check it and on success have it in a separate variable. It's more general and avoids the edge case you constructed. Hence I propose
I know that this is a bit quirky in that a variable is declared in a condition to then be scoped into a branch. At the same time, that's not unlike variables declared in loop heads being scoped into the body. Another thing to consider is that such type checks should probably only allow Here's a POC implementation via macro: https://try.haxe.org/#3ba77723 But I think we've sorta entangled two questions:
I was the one to table the second question, primarily because I think if we had syntax that is both concise and safe, we could drag along |
Regarding the cast specification change: I think we all agree that
I agree there's not a lot of very good solutions here but I think we should not let this slip for Haxe 5. |
If Somewhat related to the
|
Haxe - like most languages - has a few oddities that might confuse newcomers, although I think I would put I concede that if we were discussing adding the same functionality today, it would seem like a bad idea to make both look so similar. But it is how it is and it's not outrageously bad. Even Wikipedia distinguishes checked and unchecked casts - which fully correspond to our safe and unsafe casts. If we still want to change this (instead of properly documenting it) I definitely think that changing the spec is the worst possible way to approach it. Newcomers are great and all, but this would be a clusterfuck for existing users, with a pretty lousy migration path. A better alternative would be to deprecate both uses of I never fully understood the purpose of As for unsafe casts, they are useful in places where the user knowns that some expression will be off some value, but the compiler does not. The easiest example is right after a runtime type check of course. But in general |
ATM in Haxe 4 we have different ways to cast a type to another:
cast(expr,T)
expr
is a subtype ofT
so the cast is validStd.downcast(expr,T)
expr is T
combined with acast(expr,T)
var v : T = cast expr
expr is T
andStd.isOfType(v,T)
to check if an expression is of a given type at runtime, the Std.isOfType allowing to specify the type as a variable.I know it's a bit late but I think we should change this for Haxe 5, as this is the kind of big change we except and allow in a major release.
My proposal would be to:
var v : T = cast expr
would be the same asvar v = cast(expr,T)
.Std.unsafeCast(expr)
that would replace the unsafe cast, but then be much more explicit.downcast
to a keyword and allow bothvar v : T = downcast expr
andvar v = downcast(expr,T)
the same as the safe cast but with a null result.The text was updated successfully, but these errors were encountered: