You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I had mentioned this in dotnet/roslyn#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.
The variable pattern looks more like an inline variable declaration and should feel immediately familiar.
if(objisPerson{varlastName= LastName }){Console.WriteLine($"Hello Mr. {lastName}");}// equivalent toif(objisPerson{LastNameisvarlastName}){Console.WriteLine($"Hello Mr. {lastName}");}
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.
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 is var foo or some variation.
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(pis{Barbar= Foo }){ ...}
but the semantics is more like let here,
if(pis{letBar bar = Foo })
Which is more verbose and (I don't know the word) than is var, while with my suggestion you could still use the regular is for type checks, as you always would do
if(pis{Foovar foo })if(pis{FooisBarbar})
And as you suggested, if we use == for comparing constants, there will be no ambiguity to omit the variable in type checks,
@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 dotnet/roslyn#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:
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.
Variable Pattern and Implicit Conversions
I had proposed the syntax var x = Y as an alternate assignment-like form of the variable pattern. In the comments I also mentioned having support for a non-inferred assignment, e.g. string x = Y, which would differ in behavior from its the type pattern in that it would be a compile-time error if the type of the property cannot be implicitly converted to the specified type. Simple enough, but this glosses over implicit conversion operators.
I do think that there is a bit of a can-of-worms in allowing for the implicit type conversion to be considered. If implicit type conversions, why not also explicit type conversions, e.g.:
if(pisP{Zz=(Z)Y })
And if explicit type conversions, why not arbitrary expressions, e.g.:
if(pisP{Zz= MyFunc(Y)})
There might be some utility to supporting these kinds of expressions within a pattern, and the compiler could certainly translate it, but the question is whether its worth complicating the pattern property syntax to make it possible.
I had also proposed the equality comparison expression P == "Foo" as an alternate form of the constant pattern P is "Foo". However, similar to the issue above there is the consideration of custom equality operators as well as implicit conversion operators that may come into play if such a comparison were made as a normal expression. If "inline guards" are also considered this is probably not a big problem as the expression could be lifted into such a guard:
@HaloFour I think it's all taken care of in the pattern spec draft,
A value of static type E is said to be pattern compatible with the type T if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from E to T.
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 is operator.
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.
@paulomorgado Within the property pattern that identifier would refer specifically to the property. Any other identifier by that name in scope wouldn't matter.
So what would be the impact if is does not work with constant patterns, in general?
As currently specified is outside of property patterns doesn't accept constant patterns, per this proposal I expect something like this could be allowed in property subpatterns.
Difference between existing is operator and is operator in patterns is just that. the latter accepts constants (that can be an identifier) and the former accepts a type that is an identifier. But using relational operators there woudn't be any ambiguity anymore.
@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:
objisPerson{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
This is extensively ambiguous. How the compiler should know that which identifiers are Person's properties? That said, I think the leading identifier in property subpatterns is a must.
@bondsbw How about LastName? For each identifier the compiler should check target type (in this case Person) to see if it has a property with that name and match agaist it, otherwise it should lookup the variable in the containing scope? And what happens if you actually add such variable/property in that scope later? There will be some crazy scoping rules that is not really necessary IMO. All in all, I don't have a strong disagreement towards any of this because I'm not quite OK with the current form either. So I'd rather wait to see what it will become :shrugs:
@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
property-subpattern: identifieriscomplex-pattern
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:
varperson=newPerson{LastName="Gates"};varLastName="Smith";if(personis{LastNameis"Gates"})// ok{// person is resolved from containing scope and
...// LastName is resolved as a member on person:Person}
varperson=newPerson{LastName="Gates"};varlength=person.LastName.Length;if(personis{lengthis5})// error{// length is not a member of person:Person
...// length from containing scope cannot be used as property subpattern identifier}
varperson=newPerson{LastName="Gates"};varlength=person.LastName.Length;if(personisPerson&&lengthis5)// ok{// length is resolved from containing scope
...}
varperson=newPerson{LastName="Gates"};if(personis{LastNameis{Lengthis5}})// ok{// LastName is resolved as a member of person:Person
...// Length is resolved as a member of LastName:string}
The constant pattern can be compared using the equality operator instead of is.
Except that they have different semantics.
object o = (short)3;
System.Console.WriteLine(o == 3); // error: operator== cannot be applied to operands of type `object` and `int`
System.Console.WriteLine(o.Equals(3)); // False
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:
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 4 is not the same as saving to a variable and then comparing against 4, in part because that variable might be of type object and therefore not of the correct type. So no, it is not the same as a pattern guard.
@gafter I agree, that's what I was trying to say. If a syntax like Student { GPA == 4.0 } is considered I would treat that like a guard condition and not like the constant pattern. In that case, if GPA happened to be of type object then that comparison would be a compile-time error as operator == cannot be applied to operands of type object and double.
This discussion was converted from issue #480 on September 08, 2020 20:15.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
@HaloFour commented on Mon Feb 08 2016
I had mentioned this in dotnet/roslyn#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.@alrz commented on Mon Feb 08 2016
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
var
for variable patterns,That doesn't totally change the order of leading identifier in the property subpatterns.
@HaloFour commented on Mon Feb 08 2016
@alrz
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
is var foo
or some variation.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.
@alrz commented on Mon Feb 08 2016
@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,
but the semantics is more like
let
here,Which is more verbose and (I don't know the word) than
is var
, while with my suggestion you could still use the regularis
for type checks, as you always would doAnd as you suggested, if we use
==
for comparing constants, there will be no ambiguity to omit the variable in type checks,@HaloFour commented on Tue Feb 09 2016
@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 dotnet/roslyn#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:
I personally prefer this syntax and behavior despite its lack of symmetry with the variable and type patterns.
@alrz commented on Tue Feb 09 2016
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,
@alrz commented on Tue Feb 09 2016
Although this is contrary to the title but I'd like to be able to use OR patterns also in property patterns,
Related: #6235
@Makoto64 commented on Tue Feb 09 2016
The OR pattern is a luxury I would be thrilled to have at my disposal.
@HaloFour commented on Wed Feb 17 2016
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.
Variable Pattern and Implicit Conversions
I had proposed the syntax
var x = Y
as an alternate assignment-like form of the variable pattern. In the comments I also mentioned having support for a non-inferred assignment, e.g.string x = Y
, which would differ in behavior from its the type pattern in that it would be a compile-time error if the type of the property cannot be implicitly converted to the specified type. Simple enough, but this glosses over implicit conversion operators.I do think that there is a bit of a can-of-worms in allowing for the implicit type conversion to be considered. If implicit type conversions, why not also explicit type conversions, e.g.:
And if explicit type conversions, why not arbitrary expressions, e.g.:
There might be some utility to supporting these kinds of expressions within a pattern, and the compiler could certainly translate it, but the question is whether its worth complicating the pattern property syntax to make it possible.
Constant/Comparison Patterns
I had also proposed the equality comparison expression
P == "Foo"
as an alternate form of the constant patternP is "Foo"
. However, similar to the issue above there is the consideration of custom equality operators as well as implicit conversion operators that may come into play if such a comparison were made as a normal expression. If "inline guards" are also considered this is probably not a big problem as the expression could be lifted into such a guard:@alrz commented on Wed Feb 17 2016
@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.
@HaloFour commented on Wed Feb 17 2016
@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
is
operator.@alrz commented on Wed Feb 24 2016
I would like to suggest another level of conciseness for property patterns when the type is known.
Under this proposal,
becomes
It would be nice to be able to dot off it if you want to match a nested property,
Obviously, if
Member
wasnull
pattern fails.@paulomorgado commented on Wed Feb 24 2016
@HaloFour, are you proposing to have both forns or for your proposal to be the one proposal?
@HaloFour commented on Wed Feb 24 2016
@paulomorgado
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.
@paulomorgado commented on Wed Feb 24 2016
What would happen if a
LastName
was already in scope?@HaloFour commented on Wed Feb 24 2016
@paulomorgado Within the property pattern that identifier would refer specifically to the property. Any other identifier by that name in scope wouldn't matter.
@alrz commented on Thu Mar 03 2016
@bondsbw Commenting in the related thread.
As currently specified
is
outside of property patterns doesn't accept constant patterns, per this proposal I expect something like this could be allowed in property subpatterns.Compared to:
Difference between existing
is
operator andis
operator in patterns is just that. the latter accepts constants (that can be an identifier) and the former accepts a type that is an identifier. But using relational operators there woudn't be any ambiguity anymore.@bondsbw commented on Thu Mar 03 2016
@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:
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
Forgive me if this is already covered in pattern matching, I get lost on some of those larger threads where the ideas have evolved.
@bondsbw commented on Thu Mar 03 2016
Or even implicit conversion of infix binary operators to prefix unary:
@alrz commented on Thu Mar 03 2016
This is extensively ambiguous. How the compiler should know that which identifiers are
Person
's properties? That said, I think the leading identifier in property subpatterns is a must.Also, using dotnet/roslyn#9005
It doesn't make sense to turn "a pattern that matches against an identifier" to a full blown expression.
@bondsbw commented on Thu Mar 03 2016
@alrz Sorry, I wasn't clear since I didn't copy all of @HaloFour's example.
pattern
was used as a variable (not a placeholder for a syntax pattern):@alrz commented on Thu Mar 03 2016
@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.
@bondsbw commented on Thu Mar 03 2016
@alrz
Nonsense, it is completely unambiguous that
pattern
was a string variable. How do you come to any other conclusion?@alrz commented on Thu Mar 03 2016
@bondsbw How about
LastName
? For each identifier the compiler should check target type (in this casePerson
) to see if it has a property with that name and match agaist it, otherwise it should lookup the variable in the containing scope? And what happens if you actually add such variable/property in that scope later? There will be some crazy scoping rules that is not really necessary IMO. All in all, I don't have a strong disagreement towards any of this because I'm not quite OK with the current form either. So I'd rather wait to see what it will become :shrugs:@bondsbw commented on Thu Mar 03 2016
@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:
@gafter commented on Thu Mar 10 2016
Except that they have different semantics.
@HaloFour commented on Thu Mar 10 2016
@gafter
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:
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.
@gafter commented on Thu Mar 10 2016
@HaloFour Matching against the pattern
4
is not the same as saving to a variable and then comparing against4
, in part because that variable might be of typeobject
and therefore not of the correct type. So no, it is not the same as a pattern guard.@HaloFour commented on Thu Mar 10 2016
@gafter I agree, that's what I was trying to say. If a syntax like
Student { GPA == 4.0 }
is considered I would treat that like a guard condition and not like the constant pattern. In that case, ifGPA
happened to be of typeobject
then that comparison would be a compile-time error as operator==
cannot be applied to operands of typeobject
anddouble
.Beta Was this translation helpful? Give feedback.
All reactions