-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Proposal: Simplified Property Patterns #8457
Comments
Finally 👍 But I think inline bool expression doesn't add much and complicates the pattern production rules (that is why we have case guards). One suggestion, I'd prefer simple
That doesn't totally change the order of leading identifier in the property subpatterns. |
In my opinion it's the order that I think feels the most confusing with variable patterns. Everywhere else in the language assignment is right-to-left. I think mimicking normal variable assignment operator syntax makes it immediately recognizable as an assignment. I know that people who are more familiar with F# should be familiar with record patterns assigning left-to-right, but they would probably also not have as many issues groking I've heard similar complaints about the type pattern, in that it looks and feels weird for the identifier to be assigned to appear on the right. |
@HaloFour Downside of using variable declaration syntax for var pattern is that if you want to explicitly state the type (type check) you'd end up with something like this, if(p is { Bar bar = Foo }) { ... } but the semantics is more like if(p is { let Bar bar = Foo }) Which is more verbose and (I don't know the word) than if(p is { Foo var foo })
if(p is { Foo is Bar bar }) And as you suggested, if we use if(p is { Foo is Bar }) |
@alrz That is a potential issue, although I don't think that it's a particularly big deal. The alternative, which is what I had mentioned in my comment on #206 originally, was that when using that variable declaration pattern specifying the type name explicitly would not behave as if you were applying the type pattern. Instead it would be treated as a compile-time checked assignment: public class Person {
public string Name { get; set; }
}
public class Student : Person {
public Course Course { get; set; }
}
Person person = new Student() { ... };
// legal
if (person is Student { string name = Name, Course course = Course }) { ... }
// compiler error, CS0266 Cannot implicitly convert type 'Course' to 'OnlineCourse'
if (person is Student { string name = Name, OnlineCourse course = Course }) { ... }
// legal
if (person is Student { string name = Name, Course is OnlineCourse course }) { ... } I personally prefer this syntax and behavior despite its lack of symmetry with the variable and type patterns. |
Honestly, it doesn't look like a pattern anymore. But the rest is all good, for example omitting known types would also work with anonymous types, var p = new { Foo = "foo" };
switch(p) {
case { Foo == "foo" }: ...
} |
Although this is contrary to the title but I'd like to be able to use OR patterns also in property patterns, // distributive operators
if(a is T { P == 5 or 10 })
// disjunctive patterns
if(a is T(5 or 10)) Related: #6235 |
The OR pattern is a luxury I would be thrilled to have at my disposal. |
Having thought about these proposed syntax additions I've come up with a few additional cases where this syntax would differ from the pattern form. I don't think that this is necessarily a problem but it does need to be considered.
|
@HaloFour I think it's all taken care of in the pattern spec draft,
Using assignment-like deconstruction and separating each of these conversations in each case for a pattern would complicate those rules and wouldn't be intuitive at all. |
@alrz That doesn't cover user-defined conversion operators, only the language supported implicit/explicit reference conversions. I seriously doubt that the type pattern would consider conversion operators because that would result in a disparity with the |
I would like to suggest another level of conciseness for property patterns when the type is known. Under this proposal, expr is MemberExpression { Member is MemberInfo { Name is var name } } becomes expr is MemberExpression { Member is { Name is var name } } It would be nice to be able to dot off it if you want to match a nested property, expr is MemberExpression { Member.Name is var name } Obviously, if |
@HaloFour, are you proposing to have both forns or for your proposal to be the one proposal? |
I think I'd keep the arbitrary subpatterns around since that syntax would be used elsewhere in pattern matching. And if any of my proposed syntax must behave differently due to the expectations of the syntax then I think it would be important to be able to utilize the stock pattern matching behaviors. |
What would happen if a |
@paulomorgado Within the property pattern that identifier would refer specifically to the property. Any other identifier by that name in scope wouldn't matter. |
@bondsbw Commenting in the related thread.
As currently specified
Compared to:
Difference between existing |
@alrz Those could definitely help, but I'm not sure if I would be comfortable with having those as subpatterns without allowing the full breadth of expression syntax, such as what @HaloFour mentioned: obj is Person { Regex.Matches(LastName, pattern) } It might be more frustrating for users considering some types of expressions work and some don't. Also could there be alternative ways to accomplish the same goals? Such as e is Employee { Id is GreaterThanOrEqualTo(0) }
index is Between(0, str.Length)
obj is Person { LastName is RegexMatch(pattern) } Forgive me if this is already covered in pattern matching, I get lost on some of those larger threads where the ideas have evolved. |
Or even implicit conversion of infix binary operators to prefix unary: e is Employee { Id is >= 0 } |
This is extensively ambiguous. How the compiler should know that which identifiers are Also, using #9005 obj is Person { LastName is RegexMatch(pattern) } It doesn't make sense to turn "a pattern that matches against an identifier" to a full blown expression. |
@bondsbw I'm trying to show you the alternative way (using active patterns) to do that which I think is not ambiguous, so it's not totally impossible.
Yes, that's what you know but the compiler doesn't. |
Nonsense, it is completely unambiguous that |
@bondsbw How about |
@alrz Ok, I was under the impression that you had issue with what I mentioned. Your issue seems to be at a much more fundamental level, with the general form
and the ambiguity surrounding the resolution of identifier. But I don't think that's necessarily a difficult problem, right? At the top level of such a pattern, the identifier has to be in the local scope. Inside a property subpattern, it would have to be a member name on the object in question. Examples: var person = new Person { LastName = "Gates" };
var LastName = "Smith";
if (person is { LastName is "Gates" }) // ok
{ // person is resolved from containing scope and
... // LastName is resolved as a member on person:Person
} var person = new Person { LastName = "Gates" };
var length = person.LastName.Length;
if (person is { length is 5 }) // error
{ // length is not a member of person:Person
... // length from containing scope cannot be used as property subpattern identifier
} var person = new Person { LastName = "Gates" };
var length = person.LastName.Length;
if (person is Person && length is 5) // ok
{ // length is resolved from containing scope
...
} var person = new Person { LastName = "Gates" };
if (person is { LastName is { Length is 5 }}) // ok
{ // LastName is resolved as a member of person:Person
... // Length is resolved as a member of LastName:string
} |
Except that they have different semantics.
|
On top of the other issues that I pointed out above. I do agree that the one could not be considered an alternative to the other. At best, shorthand for a pattern guard: if (person is Student { GPA == 4.0 }) { ... }
// equivalent to
if (person is Student { GPA is var $tmp } && $tmp == 4.0) { ... }
or
case Student { GPA == 4.0 }:
// equivalent to
case Student { GPA is var $tmp } when $tmp == 4.0: While I do think that the syntax would be nice and concise and relatively easy to understand I'm worried that it does open a can of worms. This proposal is more about kicking around ideas for bridging the gap between the syntax of C# 6.0 and earlier and the proposed syntax for pattern matching, particularly for those that don't have a lot of experience with pattern matching in functional languages, which I assume to be the majority of C# developers. I think syntax similar to this would ease people into the concepts behind pattern matching and make the feature easier to grasp. But there is also something to be said about just pulling that bandaid, particularly if the syntax offers no other benefits. |
@HaloFour Matching against the pattern |
@gafter I agree, that's what I was trying to say. If a syntax like |
Issue moved to dotnet/csharplang #480 via ZenHub |
I had mentioned this in #206 but I wanted to open a separate proposal to consider it. In my opinion the current syntax for property patterns can be a little weird looking given that the properties are matched to subpatterns. I think that it would be easier to read to have simpler syntax for the simple cases.
Constant Pattern
The constant pattern can be compared using the equality operator instead of
is
. There is no real improvement to verbosity here but it appears to look more like a normal comparison. The same rules apply in that the operand must be a constant.Variable Pattern
The variable pattern looks more like an inline variable declaration and should feel immediately familiar.
Inline Guards
Inline guards allow to put simple comparison logic directly into the property pattern without having to use the variable pattern combined with
when
guard clauses.Possibly also support inline
bool
expressions:Known Type
Can omit the type name and the compiler will perform the property pattern using the known type.
Update: Fixed guard constructs since
when
doesn't apply toif
statements.The text was updated successfully, but these errors were encountered: