-
Notifications
You must be signed in to change notification settings - Fork 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: User-defined positional patterns #4131
Comments
I don't expect this feature to get much attention right now (triaged into backlog). But since variable reassignment was brought up in the context of other features, I thought this could add some background for future reference. cc @MadsTorgersen |
A practical application I consider for this feature is passing the value to a field, property or as an argument for another function call, without explicitly caring whether the result is A a = ...;
var extended = a as B { Property: < 0 };
AnotherCall(extended, ...); In that case, I reckon this proposal deserves to be split into a new issue on its own. One other practical improvement I could see here is enabling to use existing variables as the variable where the assigned result will be, which is another proposal. For example, A a = ...;
B b = null;
if (other)
{
if (a is B { Property: < 0 } b)
{
// something
}
} Currently, the only way for this to happen is either creating another temporary variable, or using the proposed |
Pattern matching with |
In the much earlier proposal #277 it was suggested to use the public static bool IsInteger(this string str) => int.TryParse(str, out _);
if (myStr is Integer()) ... That was a good idea (IMO at least!) and it would be nice if it at least doesn't get forgotten entirely in a new proposal. The advantage would be that the new syntax would automatically recognize a lot of such methods in existing code, but wouldn't erroneously assume that every bool-returning method must be a pattern. It would also avoid ugliness like For case (c) in this proposal, where the return type is I'm not sure what the use case is for void-returning patterns that always succeed so I'm not sure whether there's a prefix that would make sense for recognizing them. The idea of every void-returning method and extension method showing up as potential patterns in Intellisense seems awkward at best. Another question if the |
This was initially meant to be a context around those features as I haven't considered any usage outside the scenarios mentioned. Should eventually moved/removed once the design is fleshed out.
A variation of this was mentioned but if anything, the goal is to avoid too many existing methods to be applicable here, addressing your next point:
Either using an attribute or a naming convention. Nevertheless, it might be a good idea to cover common cases such as Try* methods. return s switch {
[.. double.TryParse(var v), 'm'] => TimeSpan.FromMinutes(v),
// ..
} (That’s not supported in the current iteration of the proposal.) |
I'd venture to guess that catching I also think there's a definite ergonomic benefit in being able to support |
@333fred You may want to use the existing championed issue for "user-defined positional patterns", or close it. I assigned to you and you can figure how you want to do it. |
User-defined positional patterns
Summary
Defining a named positional pattern which is not bound to a type check.
Exploring a different approach than the one originally presented in #1047 which suggests to relax
Deconstruct
to returnbool
.This proposal does not conflict with bool-returning
Deconstruct
methods, i.e. both can exist at the same time, but this enables much more flexibility in this space as these patterns have a name of their own rather than just using the type's name.Motivation
Currently, as soon as you want to run some custom logic as part of a match you need to fallback to using expressions and the
when
clause. This feature would enable custom logic to run as part of the match itself with the advantage of nesting in recursive patterns.Detailed design
No new syntax is required. We just use the recursive pattern syntax and bind the name to a method if no type is found.
For instance:
In a recursive pattern like that, when the type lookup for
BitPatternMatch
fails, we'll look for an instance method or an extension method on the target value, e.g.value.BitPatternMatch(0xb_1001)
is evaluated.To prevent accidental binding, we may require an attribute on those methods or use a name convention like
XXXPattern
similar toXXXAttribute
.Each argument can be a:
out
/ref
we'll pass the constant value as the argument.out
we'll match it against the result.out
we'll match it against the result.This is mostly the same as how we handle
Deconstruct
, except for the case (a-1).Some other examples:
We require the return type to be either:
bool
- in which case no variable designator is permitted and it can fail, orvoid
- in which case no variable designator is permitted and it always succeeds, orT?
whereT
is either a reference type or value type.In case (c), the stripped return type would be the narrowed type of the pattern, and also the type of the pattern variable, if any.
For example,
Integer
pattern above could be defined differently to use pattern variables:Whether or not we allow
()
to be omitted is an open question.Companion features
out
patternsWe may want to extract a specific pattern as a method:
We used
out identifier
syntax to express assignment to an existing variable. The variable type needs to match the target.The variable is always definitely-assigned after the match, regardless if it's failed in which case we assign
default
to it.We can possibly require these variables to be unassigned
out
parameters to prevent any other misusage.as
expressions with a pattern instead of a typeSince the return type of an extension pattern is the narrowed type, we can provide a shorthand for when we want to bubble up the narrowed type from an inner match:
This is compatible with the existing
as
expression, as it returnsnull
for when the type check is failed.As a consequence, we will have
x as var v
as some kind of declaration expression.With pattern semantics,
x as int
would be now allowed but it'll be equivalent tox as int?
as it requires to return a nullableT
.Further considerations
Beyond
out
parameters, we can also accept patterns in the position ofFunc<T, U?>
parameters.Just like top-level user-defined patterns, the return type must be
bool
or a nullable type which will be used as the narrowed type.All this together would enable some interesting scenarios within a pattern match:
No pattern variable is permitted inside those that are passed to a delegate.
Design meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#user-defined-positional-patterns
The text was updated successfully, but these errors were encountered: