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
Type matching #20
Type matching #20
Conversation
I personally think smart casting is a nice feature (and AFAIK it's already implemented in some languages, like Kotlin and TypeScript). What I'm not sure about this proposal is do we really need an explicit name for downcasted value, or even any new syntax? What we could have instead is something like: if (developer is Indie) {
$type(developer); // Indie
} One concern here is that if |
@nadako In that case you have to either make |
I don't understand, if a var is typed as a subtype inside a if block, shouldn't it be safe to assign a value of that subtype within that block? |
It's not safe if interfaces are involved. |
Ah, right. Well this makes sense then. It's also similar to what C# 7 is introducing, right? |
I like the general idea of the proposal, but the premise that the existing methods are without alternative is just wrong. You can do this for single checks: switch Std.instance(developer, Indie) {
case null:
case indie: indie.developGameEngine();
} With just a little extra code, you can make something work for multiple types: switch developer {
case _.as(Indie) => Named(indie):
indie.createGameEngine();
case _.as(Student) => Named(student):
student.learnToCode();
default:
} IIRC there's an open issue about optimizing the enum allocation away in cases like that, which is all that is needed to eliminate any performance overhead. I also would like to contest this statement:
Really? What have you tried? What about this? Or why not without build macros: if (is(developer, (indie:Indie)))
indie.createGameEngine();
else if (is(developer, (student:Student)))
student.learnToCode(); Sure, all of the solutions I am presenting have some shortcomings and having actual syntax for this would be better, but I see no need to base the argument on such questionable assumptions. |
@nadako Yes. Looks like it's similar to C# @back2dos |
I'd rather prefer explicit cast blocks then. |
Like @nadako suggested, |
Consider this code:
Is it safe to assume that second call to |
No it should not be safe to assume that in an effectful world..
…On Tue, Apr 18, 2017 at 10:53 PM, Alexander Kuzmenko < ***@***.***> wrote:
Consider this code:
if(someMethod().developer is Indie) {
someMethod().developer.createGameEngine();
}
Is it safe to assume that second call to someMethod() will return Indie-compatible
instance again?
It also has performance impact because of second call.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#20 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAZb-CCkx18ojFQ15ShFoIU0CIGx2Xkjks5rxSKugaJpZM4M-VVb>
.
--
Stéphane Le Dorze
http://lambdabrella.blogspot.com/
http://www.linkedin.com/in/stephaneledorze
http://twitter.com/stephaneledorze
<https://twitter.com/#!/stephaneledorze/status/74020060125077504>
Tel: +33 (0) 6 08 76 70 15
|
Yeah, now I think explicit var binding like proposed makes sense. What's not quite clear is what that expression should expand to? E.g. if(developer is indie:Indie && indie.hasMotivation()) {
crowd.throwMoneyAt(indie);
indie.createGameEngine();
} It should become something like this, right? if (developer is Indie) {
var indie:Indie = cast developer;
if (indie.hasMotivation()) {
crowd.throwMoneyAt(indie);
indie.createGameEngine();
}
} |
@sledorze Oh, sorry. Did you mean |
Yes, that's it!
…On Tue, Apr 18, 2017 at 11:59 PM, Alexander Kuzmenko < ***@***.***> wrote:
@sledorze <https://github.com/sledorze> Oh, sorry. Did you mean function
isFish(pet: Fish | Bird): pet is Fish?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAZb-OjBSENzTm4d1swMgHODmfLZCUGzks5rxTJWgaJpZM4M-VVb>
.
--
Stéphane Le Dorze
http://lambdabrella.blogspot.com/
http://www.linkedin.com/in/stephaneledorze
http://twitter.com/stephaneledorze
<https://twitter.com/#!/stephaneledorze/status/74020060125077504>
Tel: +33 (0) 6 08 76 70 15
|
That's what I was also proposing in HaxeFoundation/haxe#5167, but it's a separate discussion, I think. |
I'm not sure that is in the scope of this proposal |
@nadako your example looks fine to me. It will probably require some code copying, when there is an |
The custom nature of the Typescript solution has broader applications but
requiers separate declaration of the function and rely on the ability to
change a variable type in a specific scope (achieved by control flow
analysis in TS).
So it is more powerful (there's other possible usages from that) but has a
'higher' implementation 'cost'.
That would not be silly to consider it.
Has it is a broader subject to discuss in a separate thread, I'll leave it
to you :) ..
…On Wed, Apr 19, 2017 at 12:02 AM, Alexander Kuzmenko < ***@***.***> wrote:
I'm not sure that is in the scope of this proposal
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAZb-G7Sa8gackcFPAtMQUUlh1EvhfH7ks5rxTMDgaJpZM4M-VVb>
.
--
Stéphane Le Dorze
http://lambdabrella.blogspot.com/
http://www.linkedin.com/in/stephaneledorze
http://twitter.com/stephaneledorze
<https://twitter.com/#!/stephaneledorze/status/74020060125077504>
Tel: +33 (0) 6 08 76 70 15
|
Updated proposal to clarify that declared variable is only available in branches of code which get executed only if |
What about the more general: "var in statement evaluates to 'not null' and scopes var in statement" if (var indie:Indie = cast developer && indie.knowsHaxe())
indie.developGame(); Also gives as a generalizations: (var company = indie.company) && company.makeMoney();
var c = indie.company && var director = c.director && director.fire(); Compare if (var indie:Indie = cast developer && indie.knowsHaxe()) indie.developGame();
if ( developer is indie:Indie && indie.knowsHaxe()) indie.developGame(); Extra |
@hughsando idk, var declaration syntax does not hint any type checking for code reader. It's read more like "declare |
ping @ncannasse @andyli @waneck |
I wish to state one more time that there are far terser idioms available in Haxe already than the examples constructed to motivate the proposal. The way I see it, this proposal effectively competes with #11 since the syntax is going to be very similar. Having both would quite certainly be a source of confusion and given the choice I would argue that the other proposal is statically more sound and explicit, since you formulate something like |
Sorry, I don't see, how this proposal competes with #11. |
That check would be (as per the other proposal): switch typeof developer {
case Indie: trace(developer.developGame());
case Student:
} |
Oh, forgot about that section in that proposal, sorry |
And yet in this proposal |
Yes, you have to check Another difference is this: var developer:Indie|Student = ...;
if (developer is Indie) {
$type(developer);//Indie
developer = new Indie();//this is - unlike in your proposal - safe to do, because we're operating within the type system
} Both proposals are about runtime type checks, which is why I am saying they compete. If both are added, they will cause confusion between newcomers at the very least. Having established their similarity, let's examine their differences together:
Yes, this statement is factually correct, but it is completely besides the point. While the type of
No, this statement is factually wrong. You can do everything on a union type, that is possible to do on the intersection of its constituents. Say you have: interface Developer {
function develop():Void;
}
interface Indie extends Developer {
function createEngine():Void;
}
interface Student extends Developer {
function study():Void;
}
var developer:Indie|Student = ...;
developer.develop();//<-- this is valid, because we know that in all cases the `develop` method is available I think this one of the crucial advantages of true union types over So, let's take a step back: say I write a library with a function that consumes function makeAGame(developer:Indie | Student):Game { ... }
function makeAGame(developer:Developer):Game { ... } With union types, you can even make this distinction: function makeAGame(developer:Indie | Student):Game { ... }
function makeAGame(developer:Indie | Student | Developer):Game { ... } The first function will only accept students and indies - which with your downcasting approach would work by checking if the argument is one of both or throwing an exception otherwise. The second function will accept all kinds of developers, but it's telling me that it has special treatment for indies and students. It all boils down to this: if you wish to do a runtime type check, it really can't hurt to tell the compiler about it beforehand - which is what union types do. |
This scenario was already discussed here #20 (comment)
override function handle(developer:Developer) {
developer.performDevelopersStuff(); //I really want Developer here
super.handle(developer); //and here. Even more: i'm not an author of parent class.
if(someSpecialCase() && developer is alien:IHumanoid) {
alien.doHumanoidStuff();
}
}
This is the only real advantage of #11 i see: typehinting for API. And this is what interfaces are supposed to be used for. And if you can't achieve what you want with interfaces, then you need method overloading, which unfortunately we don't have in Haxe.
What for? Why does it need to maintain useless information? interface Developer {
function develop():Void;
}
interface Indie extends Developer {
function createEngine():Void;
}
interface Student extends Developer {
function study():Void;
}
var developer:Indie|Student = ...;
developer.develop();//So compiler has to deduct common type and type developer as Developer. Has nothing to do with Indie or Student here.
if(Std.is(developer, Indie)) { //And it's only this block, where compiler needs to know developer is actually Indie
developer.doDeveloperStuff(); //but don't forget it's also a Developer!
}
developer.develop(); //Just Developer again Alright, alright. I will counter this argument myself: you can pass |
Ok, there seems to be quite a bit of confusion here. I really suggest you read the link Stephane shared and that you stop interpreting so freely what I say. Here is a piece of TypeScript code to clarify what #11 proposes: function get(): Indie | Student {
return new Indie();
}
class Indie {
code() { }
doIndieThing() { }
}
class Student {
code() { }
doStudentThing() { }
}
get().code();//compiles, because both types have a `code` method
let dev = get();
if (dev instanceof Indie)
dev.doIndieThing();//compiles, because dev is Indie in this branch
if (get() instanceof Indie)
get().doIndieThing();//does not compile for the reasons you gave - btw.: nobody ever said it should Let's be very clear that the type is Now, if all types have a common base type, then the compiler could (and should) use that as the runtime type. The good news is, the compiler can already determine that. But again, even if that common base type exists, it is not the same as
So you are saying that compile time type information is useless? In that case I suggest you just use
In short: yes. I'm certainly curious to know what Simn thinks about #11. If you wish to make the case that it's too complicated or otherwise flawed, please do. So far you have only demonstrated a rather pronounced disinterest in understanding what's actually being proposed. |
I like the idea in general, have to think about what consequences it would have for the typer. There's a good chance this is not so easy to get right with out current architecture. |
I like the syntax, and I think it's pretty useful. This could probably be a subset of allowing variable declarations as expressions (e.g. in C++ you could write |
What about the class FewpDewp {
static var creature: Animal;
static function doAThing(){
if (pet = creature as Household mammal) {
// with 'pet' now defined within this scope, typed as HouseholdMammal
}
}
} Admittedly, I didn't read the whole thread so sorry if I'm being redundant, but that syntax was the very first thing that came to mindg |
It doesn't look like a lot of people want this, so we decided to reject it. |
Too bad, but is there still hope for this to work: if (dev is Indie) dev.doIndieThing(); // compiles, because dev is Indie in this branch |
Since it's the core of this proposal, I doubt it. |
It's a common case to check if a variable is of some type and cast it to that type.
This proposal is an attempt to improve Haxe in such situations.
Rendered document