-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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 LDM Issues in Pattern-Matching #11744
Comments
Is there a reason to change the wildcard pattern from |
v. if (q is Point(x: 3, y: 4) { Length: 5} -- yes, please! x. if (q is var p) -- Is this just picking up the null test from is and giving you another variable with the same static type and value? xi. if (q is {x: 3}) -- does this mean infer the static type of q or that there are dynamic/reflection shenanigans going on? xii. if (q is (x: 3, y: 4)) -- does this infer the static type of q which must then be deconstructable or a tuple, or does it work with ITuple dynamically? or maybe IDeconstructable? |
@HaloFour It is because @mattwar xi. A property pattern requires the compiler to know that the static type contains the named properties or fields. xii. A tuple pattern with named subpatterns like |
So is it an identifier or a wildcard? Both depending on context? What happens if you have a variable in scope named I'm fine with the concept and its use to disambiguate, I'm just a little squeamish about taking an existing legal identifier and repurposing it this way. IIRC those functional languages never allowed you to name anything |
@gafter so then 'x' just declares a new variable that has the same type and value as q? |
@gafter okay is see (x: 3, y: 4) requiring the static type so the names match, but what about (3, 4)? Does this work dynamically with ITuple or does it require a statically known type that is a tuple or has a Deconstruct method? |
None? I don't think allowing tuple patterns to match arbitrary deconstructable types would be a good idea (just like the other way around, where we don't allow construction of such types via tuple literals). Omission of the type where we specify property patterns can be useful, but when we use positional patterns, it looks really confusing. That being said, there would be no reason to deconstruct tuples via I have reservations regarding abandoning |
It is because of these ambiguities that The LDM likes the idea of being able to elide the type name when it is statically known. On the other hand, perhaps we could use |
It is syntactically an identifier (just like
That's no problem. You can't use variables in a pattern anyway.
Yes. That is sort of the point of it.
That isn't proposed. I'm not confident that would be a compatible change. |
If the type is a Tuple or has a suitable Deconstruct method, that is used. Otherwise if an explicit reference conversion exists to ITuple, that is tried. Otherwise it is an error. |
@gafter so if the static type is object then there is no dynamic discovery of ITuple? |
An explicit reference conversion exists from object to ITuple, so that works. |
@gafter just not very convenient. I can't really use it in a switch unless I expect all cases to be ITuple. |
@mattwar why not? |
@gafter because I would have to explicitly cast the expression to ITuple (or some other static type that implements ITuple) before I could declare any case expressions with tuple patterns. object e = ...
switch ((ITuple)e)
{
case (3, 4): ...;
case (4, 5); ...;
} when I might want to do this: object e = ...;
switch (e)
{
case (3, 4): ...;
case (4, 5): ...;
case string s: ...;
case int x: ...;
} |
No, you do not have to cast. The latter works because the explicit conversion exists, not because you forced it to that type using a cast. |
But if I recall correctly, one can use constants in patterns. So what if I already have _ constant? (However unlikely that is.) |
@zippec A simple identifier used as a pattern always looks it up; if a constant is found, it is a constant pattern. A simple identifier does not define a pattern variable, so there is no syntactic conflict. |
Okay, so I physically walked over to @gafter's office and spoke with him directly, with actual spoken words and stuff. Neal is using spec-language to be overly precise and possibly tease me a bit. An explicit cast from object to any interface always exists, it just may fail at runtime. Which is to say that it will check at runtime (dynamically) to see if the value implements the ITuple interface, and if it does, it will satisfy the tuple pattern. |
There are several proposals that touch on those concepts, e.g. #8074, #20. Adopting |
So just that I understand this, the identifier if (arg is (int, int)) // type test
if (arg is (int, int) _) // pattern match (assuming #10941) @HaloFour I think the wildcard being an identifier is because we don't expect a pattern in this context. By the way, I think allowing |
It seems horribly unintuitive that adding an identifier ... any identifier ... would semantically change the nature of the test. In this proposal var _ = ...;
if (arg is (int, int) _) {
var x = _.x; // uh, wha?
}
Same reason, At least |
This I agree. F# for example, has distinguishable concepts of the type test expression
Since using the identifier |
Common or not, it's still legal. I've seen it used numerous times in place of a short-hand sigil in lambda expressions:
You mean forbid access if there is more than one? Sure, that could be done. But just like with repurposing the identifier in place of a pseudo-pattern I fail to see why. The only reason I can see for wanting to use |
@HaloFour As I said, I'm thinking that this is a semantical workaround to disambiguate corner cases like tuple types vs tuple pattern, you can see an syntactical alternative mentioned in #11562,
Obviously, none of these would be a perfect solution. I think all this trouble is to not introduce any other pattern matching operators beside of |
I am resisting the idea of a type without an identifier being a pattern, which is why the if (e is (int, int)) // syntax error
if (e is (int, int) _) // type test for type (int, int)
if (e is (int _, int _)) // tuple pattern for any tuple containing two integers
if (e is (int _, int _) _) // syntax error |
I expect this to be a type test because it is already what
This is the very use case of the "as pattern" which enables us to bind the whole thing to a variable. It would be extremely unfortunate if you can't use it with tuple patterns. In fact, I don't see how it can be ambigious without #10941? |
if (e is (int x, int y)) // type test or tuple pattern? If it is a pattern, and you capture it in a variable, you'd end up with a variable of type |
It is useful in recursive patterns (not the if (e is T { TupleProperty: (2, 3) t })
let (2,3) t = ReturnsTuple() else return; Here, we don't need a variable for individual patterns ( You might want to do a type test and bind a variable e.g. I can't think of any use cases where you would want to do these at the same time and as you said, it wouldn't be useful because the type of the variable wouldn't be specific. F# doesn't support it either. |
Issue moved to dotnet/csharplang #706 via ZenHub |
Here are my top open issues for pattern matching beyond typeswitch. Making progress on these will help inform the shape of what we do for typeswitch (e.g. the grammar, syntax trees, syntactic and semantic constraints, etc).
Open LDM Issues in Pattern-Matching
Recursive pattern forms
We've discussed the relationship between positional patterns and tuple patterns, but we haven't followed up on the implications of our intuition that tuple patterns are a kind of special case of positional patterns (where the static type of the matched expression is used as the type of the pattern).
ITuple
)?e
is of typeobject
, andq
is of typePoint
, which has a methodDeconstruct(out int X, out int Y)
)if (e is Point p) ...
if (e is Point {X: 3}) ...
if (e is Point(3, 4)) ...
if (e is Point(X: 3, Y: 4)) ...
if (e is Point(X: 3, Y: 4) {Length: 5}) ...
if (e is Point {X: 3} p) ...
if (e is Point(3, 4) p) ...
if (e is Point(X: 3, Y: 4) p) ...
if (e is Point(X: 3, Y: 4) {Length: 5} p) ...
if (q is var p) ...
if (q is {X: 3}) ...
if (q is (3, 4)) ...
if (q is (X: 3, Y: 4)) ...
if (q is (X: 3, Y: 4) {Length: 5}) ...
if (q is {X: 3} p) ...
if (q is (3, 4) p) ...
// disallow? see Note 1 belowif (q is (X: 3, Y: 4) p) ...
if (q is (X: 3, Y: 4) {Length: 5} p) ...
Point
appears above? (Beware ambiguities; note that3
,4
, and5
stand in for arbitrary patterns)ITuple
. A "no" answer helps eliminate an ambiguity between 1 and 16 above.Deconstruct
method has a singleout
parameter.Other syntactic forms
We have proposals for the following additional syntactic forms:
match
expression for aswitch
-like expression form_
, and allow that identifier to be used for disambiguating the recursive pattern forms.guard
statement that expands the scope of pattern variables declared within it.let
)let
)@dotnet/ldm For your consideration
The text was updated successfully, but these errors were encountered: