-
Notifications
You must be signed in to change notification settings - Fork 58
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
Single pattern checks #79
Conversation
Proposed syntax variants are too obscure. Anyway I'd like to have single pattern matching with var capturing, but I don't see how to fit it in Existing |
This is in part because currently single pattern check is usable only on enums and switch is verbose, so structural and tuple pattern matching is not as accessible for non-enum types.
It can be renamed from
If // test
option match Some(int) where int % divisor == 0;
// condition
if (option match Some(int) where int % divisor == 0) {}
// cast
option cast match Some(int) where int % divisor == 0; As i understand, new syntax is better not to start with a keyword itself because if it is then the word becomes reserved one.
Cast does what cast does - tries to convert from less specific ( |
Except that that looks more like an unsafe cast, which doesn't throw. I think there's already enough confusion around casting in Haxe, let's not add more... |
In general, I agree it would be nice to have a shorthand, but in some cases, this is not really working out so well: if case (option, Some(int), int % divisor == 0) {
then(int);
} else {
or();
} I'd stick to what's available: switch option {
case Some(int) if (int % divisor == 0):
then(int);
default:
or();
} It doesn't cluster keywords, but instead uses them to set the various subexpressions apart (instead of relying on I'm not big fan of that
Personally, I think it would be better with a solution that expands the existing
There are a few details to hammer out, especially around This approach wouldn't require any new syntax and would extend an existing feature in a relatively intuitive way to solve the problems motivating this proposal. |
Unsafe has one argument (expression), safe - two (expression and the type). This has two. I chose
Right now more than 10% of pattern matching defaults in haxe codebase simply throw an error. This is not about promotion, this is about optimising already popular case. And it allows not only to throw, but to do anything ( I am okay with making doing nothing a default (also a popular thing, little less than throwing an error, but still more than 10%). That might be better actually, since no need to think about message/name for default exception. And if it does not throw by default then there is no need to communicate this and space of suitable words increase.
Ok, "assertion" is a wrong choice of a word. It allows you to introduce multiple captured variables into current scope and to exit early. This is not about development checks.
Examples provided were intended only for syntax demonstration, there are not complex ones.
That's an interesting idea. |
Renamed shortcuts to one with
Renamed I said that (Regarding alternative of Maybe in extraction variable has to be declared with |
@Gama11 just a thought on scopes and guards, maybe we could use the same syntax as a regular switch case: option.match(Some(int) if(int % divisor == 0)) As this avoids any complexity with scope (as a user it’s confusing when identifiers can escape their parenthesis scope) |
As for data extraction: option.match(Some(int) if(int % divisor == 0), {
// Block with `int` available if `option` matched
}); |
@kLabz that would work but could get unwieldy because in the example your branch code is now running in your condition: if (!option.match(Some(int) if(int % divisor == 0), then(int))) {
or();
}
I think the benefit of match is when you want to concisely check an enum value and get a boolean but if you’re going to branch and use extracted values a switch is probably the best way and not less concise |
That was not intended to be used as a |
Propositions with
Parenthesis do not have their own scope. They use parent one. (final int = 3);
trace(int); // traces 3 How this can be made more clear?: option capture match Some(final int); Maybe such construction will work for you (but it will be a little harder to implement): final int = option capture match Some(int); The whole point of |
Added variables whitelist to extraction match. With this change |
I think this could be frustrating to read when the proposal is so clear in your mind but here's the verbatim thoughts that go through my head as a user seeing it for the first time option capture match Some(final int);
However, I think your simpler expression without option match Some(int) if (int % divisor == 0)
I understand the motivation for wanting a short match syntax and agree we could do something here, but I don't think I see the motivation for short extraction syntax when an expression like final int = switch option { case Some(int): int; default: null; } Is clear and not that much longer, I can see how the If we do decide that a short-hand extraction syntax is important, maybe we could do it by allowing a slimmed switch statement: final int = switch option Some(int): int else null; |
Yes and simple
Besides lacking multi variable support, you need to compare this two expressions for clarity: final int = switch option { case Some(int): int; default: throw false; }
// vs
final int = option extract match Some(int); Where instead of final int = switch options { case {
x: Some(int)
}: int; default: throw false; }
// vs
final int = options extract match {
x: Some(int)
};
Like the ideas there. |
Removed vars whitelist to avoid confusion about type of An example for why var fProp = cast(fType, FProp);
// access get and set on fProp Currently to do the same with enum you write this: switch (fType) {
case FProp(get, set, _, _):
// access get and set through variables
case _:
throw false;
} Access to With extraction: fType extract match FProp(get, set, _, _);
// access get and set through variables Alternatively extracting match can be made to return an anonymous structure with captured variables: var fProp = fType extract match FProp(get, set, _, _);
// access get and set on fProp |
Renamed Alternative syntax using // test
case is (option, Some(int), int % divisor == 0);
// condition
case if (option, Some(int), int % divisor == 0) {};
// capture
case vars (option, Some(int), int % divisor == 0); With // test
option case is Some(int) if (int % divisor == 0);
// condition
option case Some(int) if (int % divisor == 0): {};
// capture
option case vars Some(int) if (int % divisor == 0); |
Changed design to last commented alternative. (And to repeat - How do you find it? |
6004fb6
to
93d7ce6
Compare
Honestly this doesn't look like haxe at all to me, more like a succession of [random] keywords. |
Instead of: option case is Some(_); You would prefer to always write this?: option case Some(_): true else false; Checking whenever pattern matches or not is very common scenario that's why it grants its own shortcut and this is what
switch option { case Some(int) if (int % divisor == 0): {} case _: }
// shortcut
option case Some(int) if (int % divisor == 0): {} So just because |
No, I meant |
Well, wouldn't it cause the scope and type confusion of |
Well... I appreciate that you put so much thought into improving Haxe as a language and I don't mean to shut you down, but at the same time I feel a bit bad if I think you're just wasting your time here. While I acknowledge that there are certain limitations with I also sometimes want some notion of "match and extract", but I usually don't want it enough to justify the addition of an entirely new syntax. Maybe if we find something that looks just right, but so far that hasn't been the case. Overall, I'm sorry to say that this proposal has no chance of being accepted. I'm fine with keeping it open to discuss the matter further, but please be aware that this isn't going to make it into the language. |
Thanks, i'll close this then. At the moment, i don't see how it can be "more haxe" than literally being syntax of a single case switch without word "switch" (with requirement of using switch option { case Some(int) if (int % divisor == 0): {} case _: }
// shortcut
option case Some(int) if (int % divisor == 0): {} |
As the discussion here is interesting, I'd like to add that destructuring (HaxeFoundation/haxe#4300 with var/final and enum matching) might meet some use cases. For example in Rust a pattern can be used in a let statement which can further be used in an if let construct: Which in haxe could look something like: // compile error: https://doc.rust-lang.org/book/ch18-02-refutability.html
final Some(int) = option;
// but:
if (final Some(int) = option)
trace(int);
// dealing with conditions?
if ((final Some(int) = option) && int % divisor == 0)
trace(int); |
Yes, destructuring would have been useful in that capacity, shame that it was shut down for not being simple enough. |
As destruction can handle extraction of captured variables Removed mentioned shortcut. // test
case(option, Some(int), int % divisor == 0);
// condition
if case(option, Some(int), int % divisor == 0) {}
Alternatively it can be |
To iterate on extractor variant and differentiate between a test vs a condition (which should not be able to be used in composition) and help with parsing while nested in // test
is case option => Some(int)
// conditional
if (case option => Some(int)) {} Alternative is to use same approach with infix // test
is option case Some(int)
// conditional
if (option case Some(int)) {} |
Maybe we can try to alter it somewhat, using the same idea as in: if (option.match(Some(int)) && int % divisor == 0) {
then(int);
} else {
or();
} The latter seemed a very natural extension of the already existing The latter also had the advantage that there is no confusion because of the subsequent
I would say the postfixed Then if we consider 8/10 times it would be used without a "guard", like this:
If we consider that case, I start to actually prefer your proposal... Now how about we alter the
Seems it then becomes very natural. |
That's right and this is one of problems that design should overcome. But using Another limitations is that One option not mentioned before is to go for is case option => Some(int)
in case option => Some(int): {} Allowing to keep using is option case Some(int)
in option case Some(int): {}
Also is case option => Some(int)
for (case option => Some(int)) {} option case Some(int)
for (option case Some(int)) {} |
After some internal polling i see that there is no consensus about which syntax is an acceptable one. |
Might be a bit late to the party, but I would like a nice simple version of this too. I like just if (e == EnumName) { ... } Just keep this consistent syntax and extend it: if (e == EnumNameAndValue(v) ) { trace(v); }
// also ? operator
var x = e == EnumName ? 0 : 1;
var x = e==EnumNameAndValue(v) ? v : 0;
// and
if (e == EnumBool(true) ) trace("yes it is"); Ie, simply a shortcut for: switch(e) { case COND : STATEMENT; default: ELSE }
=
if (e==COND) STATEMENT else ELSE
or
e==COND ? STATEMENT : ELSE; which could be applied at the AST level without changing the backends, |
I would prefer to have a pattern check (and destructing) shortcut that is not limited to enum values. (Also extraction in |
lean-reject: questionable benefit |
Proposal to rename
.match
method for single pattern checks to.case
for it to work on any type, not only on enums.Render.