-
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: Extension Patterns #9005
Comments
Using
// complete patterns
public static (double, double, double) operator is HSB(this Color color) {
return (color.GetHue(), color.GetSaturation(), color.GetBrightness());
}
// partial patterns
public static (double r, double theta)? operator is Polar(this Coordinates coordinates) {
return coordinates.X != 0 || coordinates.Y != 0 ?
(Math.Sqrt(coordinates.X * coordinates.X + coordinates.Y * coordinates.Y),
Math.Atan2(coordinates.Y, coordinates.X)) : null;
} In this case, we might need oneples, public static (double)? operator is Double(this string str) {
return double.TryParse(str, out var value) ? (value,) : null;
} And if this "helper pattern" has more than one possible outputs, enum class Parity { Odd, Even }
public static Parity operator is(this int num) {
return num % 2 == 0 ? Even() : Odd();
}
// complete
n match(
case Odd: ...
case Even: ...
) The last one is more of a conversion operator though. |
I mention this in my second concern at the bottom of my proposal and I do expect that the user-defined is operators will move to tuples in some form like that. However, without seeing what the team has proposed I'm not going to make any guesses and I'm simply going to work with what they've already provided. The other problem is that I don't really know how I would combine a tuple return type with other input parameters without the syntax having to get weird in one way or another: public static class ConversionPatterns {
public static (int)? operator is Integer(this string input, NumberStyles style) {
int value;
return int.TryParse(input, style, null, out value) ? (value,) : null;
}
}
...
string input = "...";
// accept parameters first?
if (input is Integer(NumberStyles.AllowHexSpecifier, var result)) { ... }
// accept parameter list, then deconstruct?
if (input is Integer(NumberStyles.AllowHexSpecifier)(var result)) { ... } Not to mention the last time "oneples" was mentioned it was more or less dismissed, so honestly I don't know how the team would expect to define these operators if they were to deconstruct a single value, short of using a |
This whole issue could be neatly addressed by first ensuring the public abstract sealed struct Option<T>
{
struct None;
struct Some<T>(T Value);
} Then @HaloFour's Polar example becomes: public static Option<(double r, double theta)> operator is Polar(this Coordinates coordinates)
{
return coordinates.X != 0 || coordinates.Y != 0
? new Some<(double r, double theta)>(
Math.Sqrt(coordinates.X * coordinates.X + coordinates.Y * coordinates.Y),
Math.Atan2(coordinates.Y, coordinates.X))
: new None();
} That gets rid of both the |
It doesn't get rid of any smells, it just replaces one for another. I will note that changing the user-defined |
We already have |
I guess it's a toss up between being purist and "doing it right" and being pragmatic and "doing it now". I've no idea how the various Microsoft teams involved with future releases of Visual Studio, C# and .NET coordinate things and therefore how feasible the "doing it right" option is. |
You still have to assign a value to the tuple items. So assigning a value to the |
A small (in size) but meaningful addition to the proposal. |
@paulomorgado Nope, if the pattern is not successful you don't need to assign every tuple items, just return |
@alrz, assuming |
@paulomorgado "everyone is trying to get rid of them" as it turns out, no. I personally prefer an |
"everyone is trying to get rid of them". If only that were true. There's a strong movement to minimise the "evils" of @alrz, |
With all of the obsession over Let me reiterate, the signature of these pattern methods is based on what is currently in the pattern matching specification. If that spec changes then I will certainly address those changes in this proposal. There are two tenets of this proposal:
|
Reminds me of F# active patterns. |
I think this is a great idea. I'm not too keen on the use of I particularly like the idea of generic extension patterns. This is definitely one of the best proposals related to pattern matching and I think it would provide enormous value. |
To implement the public static bool operator is Member(this Expression @this, out Expression expr, out string memberName) {
return @this is MemberExpression { Expression is out expr, Member is MemberInfo { Name is out memberName } };
} As I suggested in #8457 to use public static bool operator is Member(this Expression @this, out Expression expr, out string memberName) {
return @this is MemberExpression { Expression out expr, Member is MemberInfo { Name out memberName } };
} The target will be matched against static type of the This requirement probably make |
@alrz If anything it's definitely a good argument for having the custom If I recall the question about pattern matching populating existing variables has been brought up before and was dismissed. After finally finding the comment on the tuplification of custom |
@HaloFour Yeah I saw your response and that seems a pretty reasonable argument. The thing is that I don't know why it's dismissed, but for this specific scenario, returning tuples for |
I disagree with that assertion in the strongest possible terms. Methods should obey the single responsibility principle; they should do only one thing. Ergo, they should only return one value (accepting that that might be a composite value, such as a tuple, or other So either C# is a fundamentally flawed language that has code-smell idioms; or |
public static (Expression expr, string memberName)? operator is Member(this Expression @this) {
return @this match (
case MemberExpression {
Expression is var expr,
Member is MemberInfo {
Name is var memberName
}
}: (expr, memberName)
case *: null
);
} There is a third-option: C# is a language that has features of which you disapprove, but are fundamental features nonetheless. |
@DavidArno Returning multiple values doesn't have anything to do with the method doing more than one thing. If that was the case you shouldn't return anything more than primitives, otherwise you are practically returning more than one thing. Although, I believe primitives also contains multiple bits, which according to your assertion, violates the single responsibility principle, indeed. @HaloFour I don't know what's your side! First you say it's a shame to make a feature dependant on a specific .NET framework version and now you are defending it. I didn't say it wouldn't be possible, I'm saying that it doesn't necessarily ease the procedure as a common practice and built-it functionality (defining helper patterns). My first reason to prefer tuples over Related: #1757 |
I'd like to think/hope that I could be pragmatic when it comes to language design discussions like this. I am of the belief that a language feature shouldn't require a BCL upgrade unless there is a compelling benefit or requirement. Being able to use simple I still see there being some issues with that approach, however, specifically any results containing less that two values. If there isn't a "one-ple" syntax then you may be forced to use the I do still have my own reasons for preferring the |
@DavidArno C# is a language which continues to evolve and grow rapidly. This implies that many different idioms will be born and survive to coexist for many years. Out parameters have a set of use cases for which they work very well, but many of these use cases will likely be superseded by tuples. There are however certain performance critical scenarios for which out parameters will remain a preferred option. |
Sure there are edge-case uses for Care must be taken too that such optimisations are the result of genuine performance bottlenecks, rather than misguided premature or micro-optimising. That's why it's a code smell feature: in most cases (though not all), they are a sign of bad design.
You missed the bit where I talked about compound values. |
@DavidArno the fact that they are uncommon cases, does not make them antipatterns or code smells. Premature, useless, or unnecessary optimizations are indicative of poor programming. Language features that facilitate such optimizations are not indicative of poor language design. I fundamentally disagree with the assertion that a feature which should only be used in rare circumstances should be considered a "code smell feature," whatever that means, for no other reason. Do you consider pointers to be a "code smell feature"? |
There's also the entire family of Anyway, this proposal isn't about the benefits (or lack-thereof) of |
I don't think I explained myself very well, as your summary doesn't reflect what I was trying to say at all! :)
Idiomatic code is the "good practice" way of doing common tasks in a language. For example, using events to achieve listeners is idiomatic in C#; implementing listeners via the GoF Observer pattern isn't.
That's actually back to front to what I was trying to say. "Code smell features" are features that should only be used in rare circumstances. @HaloFour, int x;
if (int.TryParseInt(someString, out x))
{
DoSomethingWithX(x);
}
else
{
Console.WriteLIne($"{someString} isn't an int.");
} versus if (someString.TryParseInt() is Some(var x))
{
DoSomethingWithX(x);
}
else
{
Console.WriteLIne($"{someString} isn't an int.");
} The latter case has less code and no forced variable variance ( Even better would be: match using (someString.TryParseInt())
{
case Some(var x): DoSomethingWithX(x);
default: Console.WriteLIne($"{someString} isn't an int.");
} and that really should become the one true idiomatic use in C# 7. Of course there are two potential blockers to this: your suggestion that the messy |
You can opt-out of using every single one of those features in any new program. However, you're always required to create at least one class, even for "Hello World". Anyhow, thanks for turning this proposal-that-has-nothing-to-do-with- |
By itself, I guess it doesn't make much sense. I should have kept going and included static classes and methods, primitives and structs as well as they are all examples of features that some OO purists argue made even early versions of C# a hybrid, rather than a strictly OO, language. |
I agree it is unfortunate that this proposal ended up as a long debate over |
Considering that you trotted out the "code smell" argument regarding
An editorial blog post by you does not signify an authoritative consensus on the subject. That said I don't doubt that there is such a body because in programming there is a body that disagrees with a great deal about every paradigm in every language. I'm not particularly interested in repeating 50 year old arguments. The beauty of the CLR is that it allows for a great many different languages and paradigms. There's little reason to drag this one language back and forth between competing paradigms and changing idioms. You are free to choose the tool that fits your opinions. |
Back on topic: |
string value = "...";
string pattern = ".";
if (value is RegexMatches(pattern, var match)) {
Console.WriteLine($"Matched {match}");
} How is the parser supposed to know what to parse as a pattern and what to parse as an expression? |
@gafter Input parameters are expressions, output parameters are patterns. |
@HaloFour And how is the parser supposed to know which is which when it is parsing a pattern-match (i.e. long before semantic analysis), e.g. if (expr is Name(M(), M())) See, the first one is an invocation expression so when we look-up I guess you might argue that we should completely redesign the syntax for patterns so we can unify them with expressions. |
@gafter I see. I don't know how to answer that question.
Reading that statement in my head it could have so many different inflections that it's a little difficult for my parser to determine your intent. (see what I did there? 😄) If said unification could lead to more interesting scenarios with pattern matching then I could see that being worth it, but I have no idea what that would entail. Note that I'm not married to the syntax that is in the proposal. I use it to illustrate the two concepts I'd like to see adopted, specifically creating custom |
@gafter But only constant expressions are allowed inside patterns. Currently as specified, the only expression that you can use in a pattern is a constant-expression (via constant-pattern) right? So I assume same would apply to an "extension pattern", string value = "...";
const string pattern = ".";
if (value is RegexMatches(pattern, var match)) {
Console.WriteLine($"Matched {match}");
} All input parameters must be a constant. As you said about indexer patterns (#5811):
|
@alrz The fact that a pattern can only be a constant does not help disambiguate the syntax very much. |
@gafter Right, and in an indexer pattern, the constant argument is not the part that is going to match. |
Issue moved to dotnet/csharplang #481 via ZenHub |
Problem:
The current pattern matching proposal spec defines "User-defined operator is" as being an operator defined on a type which defines how a value may be matched to that type, optionally deconstructing values which may be used in recursive patterns.
In my opinion the problem with this is that it requires defining a new type for each custom pattern. This might be fine for patterns to translate between types, however for utility/helper patterns it creates an odd syntax of individual classes named after their operations.
Solution
This proposal suggests that in addition to the
is
operators above that there also be named extensionis
operators that can be defined in other static classes and resolved in the same manner as extension methods.The return type of the operator may be either
bool
orvoid
. If the return type isbool
a return value oftrue
indicates that the pattern matched successfully; otherwise a return value offalse
indicates that the pattern did not match. If the return type isvoid
then the pattern is guaranteed to match.The first parameter of the pattern is the type of the value to be matched. I am using the keyword
this
in the examples to indicate a similarity with extension methods.In addition to permitting deconstruction of the value through
out
parameters I also propose allowing for input parameters that allow passing values to the extension pattern which may be used at affect the behavior of that pattern. As with any method the argument passed to an input parameter must be any expression of the type of the parameter. Theout
parameters indicate the deconstructed values from the pattern which may be used with recursive sub-patterns.Generic extension patterns would expand the utility of helper patterns:
Concerns:
this
argument) I do think that it would likely be a good convention that theout
parameters be last.out
parameters may be deprecated from user-defined patterns in favor of tuples. That would certainly affect this proposed syntax. I've not seen any examples of the new syntax or how it may be used. re: Outline of C# 7 demo at MVP Summit 2015-11-02 #6505 (comment)The text was updated successfully, but these errors were encountered: