-
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
Proposal: Positional deconstruction based on existing constructors and properties #8415
Comments
Personally I'm not a fan of the fuzzy name matching and would prefer an explicit method like unapply() in Scala |
I am not sure I like positional pattern matching for records. For tuples, yes. Consider the Person example above; if the order of properties change, the pattern-matching code still compiles but doesn't do the right thing anymore. I would expect using records instead of tuples to shield me from this kind of accidental mistake. F# names record properties in its pattern matching syntax:
|
@asik It would be based on the order of constructor arguments, not properties. If the constructor arguments were to be reordered you'd have lots of other problems as well. I do agree that trying to fuzzy match property names to argument names sounds like it could be an issue. I figure that this is to avoid having to write a custom You would still have explicit property patterns: switch (record) {
case Person { First is "Alan", Last is var last }: ...
} Perhaps they could be shorted to omit the type name when the type is known at compile time? |
This proposed language feature in fact relies on the brittle assumption, that the author of the class Person was sane and polite and assigned in the constructor the proper parameters to corresponding properties. |
What about the following: A constructor is a "record constructor" if it definitely assigns (directly or indirectly) each of its parameters to exactly 1 property of the same type. In other words, at all non-exceptional exit points from the constructor each of the constructor parameters has been definitely assigned to exactly 1 matching property. Names/casing shouldn't matter - we will never design a fuzzy matching scheme that will satisfy everybody. For example, what about C++-inspired |
@apskim The compiler wouldn't be able to verify that with existing types in other assemblies, short of analyzing the raw IL of the constructors and all of the properties to try to match field assignments. |
I agree with @apskim The compiler could perform some sort of flow analysis of constructor, find which parameter is assigned to which property and write this info into special attribute added to the constructor. This attribute could be then used in pattern matching to pair the arguments with properties. This could solve the naming issue. This of course requires recompilation of existing assemblies. |
@HaloFour Sorry, I misread the proposal. I assumed this was based on new record declaration syntax that didn't include an explicit constructor, something like |
@asik I think that this is specific to classes that aren't "records" but are really close to them. I assume that "record" classes defined with primary constructors would define a proper |
Some questions about the details:
|
I would propose we'd use the same comparison operator VB uses for names. Which you can see here: I personally like this proposal though not in isolation. Specifically, i think this is a sensible proposal that will get pattern matching working for a huge swath of existing code out there. On top of this i'd like some system to be made available for the types that this pattern doesn't work on. If we have both, then i think that's going to make pattern matching very viable with very low friction. |
I believe this might be ok for VB, but while the result is something that would worth it, it doesn't really fit into C# strictness and case-sensitivity. I would suggest support "extension Also, for record-like types like |
I agree with @asik. I don't like positional matching, it is brittle. We shouldn't be adding new ways to write brittle code into the language. Named parameters were a god-send for C# - they let you write self-documenting code that you know would always work correctly regardless if someone reordered parameters in a method. This feels like a step backwards from that. |
I like idea to make existing but non-record assemblies compatible with pattern match but definitely dont like current proposed implementation which is fragile to rename and assume sane programmer or correct pattern in class definition. I like short code but as soon as short code mean have error prone code then to me this is no-no (to me being fragile is acceptable only if feature bring significant value or performance boost but there is just syntax sugar). EDIT "removed" later comment since to me seems that proposed pattern is already on table with |
@BreyerW I think the point is to be able to use positional deconstruction, property patterns already would work with any type. |
I like the idea of trying to make pattern matching work with "legacy" types. The constructor parameter to Property matching proposal seems reasonable, however they aren't base on any existing C# specs. Just conventions not followed by every library's developer. I guess this fuzzy matching is outweighed by the usefulness of the feature. |
I don't think I can like this. Enabling pattern matching on existing types could be done using extension |
I don't like this. I'd rather have things be explicit than the compiler guessing based on some ordering. In the example if (o is Person p && p.FirstName == "Alan") { WriteLine(p.LastName); } What's wrong with writing this as is? It's much much clearer IMO. I really feel like this doesn't fit for C# |
While I'd love the idea of existing types getting use in pattern matching, isn't this very dangerous, though? I'm referring to the idea of assuming that a property and a constructor argument with the same name actually refer to the same data. Can the compiler really ensure that the data passed in has not been transformed in any way, and actually has been assigned to the property? This could create huge developer surprise; consider the following object:
Let's ignore the fact that this class violates a few design pricipals; it's still valid C#. You still have the crazy situation where You can imagine many other things that might be done to an object before it's assigned (strings may be trimmed or have their case changed) or some might only be assigned given a guard condition is met. How could the compiler fix these cases? I'm sure their are examples in the core library that would suffer from this. |
@Richiban, just for the record, what design principals are being violated? This proposal does not intend to mean that |
Principally (a ha.) the principal of least surprise. I wouldn't like to work in a domain where I think there's a real issue with giving ordinary classes a record-like syntax without enforcing record-like behaviour (i.e. you get out what you put in). Can't we have |
@Richiban Agreed. If the property pattern syntax was a little less verbose and ... wonky, I don't think there'd be much issue using that instead of the record pattern or |
@HaloFour Eugh, yeah; "is var" is certainly wonky. One of the things that's both pleasing and helpful about F#'s pattern matching is the syntactical symmetry you get when constructing and destructuring. If we could do the same in C# then we'd get the easy-to-understand It's a shame your comment on #206 seemed to go by unnoticed. |
reverse-engineering the relationship between positions and properties by examining the names of constructor parameters. See also dotnet#8415
Positional deconstruction based on existing constructors and properties
Proposals for pattern matching such as this (previously #206) allow a positional form of recursive pattern
Type(p1,...,pn)
, as inSo far we have assumed that some special declaration around
Person
is needed to allow this: eitherPerson
is a special kind of type (a "record"), or it has some form of "extractor" declared on it, e.g. a method of a certain shape, or a special new kind ofis
operator declaration.This comes from the need to get at the values of the object by order instead of by names of its properties. An extractor-based approach would interpret the positional pattern match somewhat like this:
The need for a special declaration means that no existing types would benefit from this out of the gate. This seems a shame, in that most existing "record-like" types do specify an order - in the form of their constructor parameters:
Ignoring the different casing imposed by naming conventions, it should be pretty obvious that the property
FirstName
corresponds to the constructor parameterfirstName
andLastName
tolastName
. If we assume that the order of constructor parameters is what the type author thought of as "the natural order" of the type's constituent values, it would be fair to expect a positional match to just use the same order(FirstName, LastName)
. The codewould then have the much simpler translation
Not only simpler, this is also how any human would have written the code if they didn't have positional matching available.
Proposal
The common case for this would be really simple, but here's a set of more general rules, in case there are multiple constructors. This is just a first whack at the rules; I'd love to hear feedback on the general idea even more so than the details:
1. Identify the set of "record constructors"
A constructor is a "record constructor" if each of its parameters has a corresponding property with the same type and a name that is "similar enough". The precise meaning of that is up for discussion; here are some options:
FirstName
would matchfirstName
)FirstName
would matchfirstname
)_
(FirstName
would matchfirst_name
)2. Identify the set of "eligible constructors"
A record constructor is "eligible" for a positional match if it is accessible and the subpattern in each position is applicable to the type of the corresponding constructor parameter (by the same rules that would lead us to flag a match as illegal at compile time because it could never succeed)
3. Identify the best constructor
Use the rules of betterness in overload resolution to decide which of the eligible constructors is the best. If none, error.
4. Encode the match
Encode the pattern matching logic such that a subpattern in a given position is matched against the property corresponding to the parameter in the equivalent position of the selected constructor.
Issues
The text was updated successfully, but these errors were encountered: