-
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
Discussion: Scope of pattern variables, and tuple decomposition #4781
Comments
What's the reasoning behind using Or is it meant to differentiate between pattern decomposition and normal assignment? Also does this mean that (using examples from #206) |
@svick Yes, this is different from "normal" assignment. You're suggesting it could be thought of as an assignment expression inside an expression-statement, but the left-hand-side isn't an lvalue, the assignment itself doesn't appear to have a well-defined type, and the left-hand-side appears to be a type expression (from the tuples proposal #3913) rather than a valid syntactic form for an (lvalue) expression. This would fill in the "Deconstruction syntax" for #3913. No, |
Speaking purely from a usability perspective, adding I understand it doesn't behave like a normal assignment, but it should still re-use the same |
Looking at current version of the language, this is similar to Keeping things local reduces cognitive load. Even in current C# I sometime use curly brackets to make a local variable even more local: public float BigCalculationThatShouldntBeSplit()
{
float result = 0;
{
float variableWithSomeMeaning = 0;
//first leg of calculation
}
{
float variableWithOtherMeaning = 0;
//second leg of calculation
}
return result;
} |
What if it will be allowed to define custom operators like in F# and then public static ..T := (this (..T args) tuple)
{
foreach (let arg in args)
{
yield return arg;
}
} |
There is also the question of how to handle the scoping of pattern variables appearing in the various parts of a query expression. I think they remain local to that expression. |
@dsaf Adding custom operators seems outside the scope of the discussion on pattern matching. It also hasn't even been seriously considered for C# 7.0 at this point so I don't think any proposal should incorporate that. @gafter I think some more examples on how the proposed scoping rules would behave (especially in contrast to the discarded declaration expressions proposal) would be helpful. |
@MgSam It is very simple - the variable simply would not be in scope outside the immediately enclosing statement. M(x is int i ? i : 12); // i is scoped to this statement
Console.WriteLine(i); // error: i not found In an if statement, it isn't even available in the else clause if (x is int i)
{
// int i is in scope
}
else if (x is string i) // int i not in scope here, so we can reuse the name
{
// string i is in scope
} Other cases
|
Paren in the wrong spot there? I recall the argument that erupted from the scoping of declaration expressions. I was under the impression that was part of the reason that the feature was dropped/postponed. |
|
@HaloFour The scoping rules for declaration expressions didn't make sense for pattern matching. That was part of the reason we put the feature off. I'm trying to restart the discussion in the context of pattern matching. |
@AdamSpeight2008 There is no identifier in the syntax for the decomposition operator. The syntax is pattern := expression ; |
@gafter So I couldn't decompose a tuple into different variables.
|
@AdamSpeight2008 I think you're talking about range variables, which have nothing to do with the current discussion. |
It needs to be a lot clearer, in its intent. For example case ( int, int ) into x, y when ( ( x > 0 ) && ( y > 0 ) ) :
// foo This checks the types, decomposes the value into variable named x and y (both of type int). If ( (t.Item1 is int) && (t.Item2 is int) )
{
// (into) section
int x = t.Item1;
int y = t.item2;
// (when) section
if( ( x > 0 ) && { y > 0 )
{
}
} |
Yes, when you start seeing a new syntax you have to take time to learn it instead of forging ahead without understanding. Hopefully you would not have to learn a new syntax every time you see it for the rest of your life. This particular new syntax can always be distinguished by the identifier appearing after the type in an The syntax you propose does not extend naturally to recursive pattern matching. I have no idea how it would be used in a method call per the original example. It also detaches the type names used to declare the pattern variables from the appearance of the pattern variables, making the syntax more difficult to comprehend, especially as the use cases get more complex. |
@AdamSpeight2008 In my opinion the syntax is as clear as it gets. But I guess you will be able to use the expression form of the M(switch(x) {
case int i:
return i;
default:
return 12; }); |
In Nemerle is would be something like M( match(x)
{
| ( int ) into i => i;
| _ => 12;
} ) @dsaf The syntax is ambiguous in terms of possible meaning.
|
Every new feature uses syntax that used to be an error. That's how we manage to extend the language without changing the meaning of existing programs.
No, it would not. It would be more like M( match(x)
{
| ( i is int ) => i
| _ => 12
} ) |
If the opposite is done and M(x is int i ? i : 12);
M(x is int i ? i : 12); Or would the second statement attempt to use the existing If not then I'm sure that there will be cases where it would be useful to have |
@mikedn No, it won't reuse an existing variable. If there is already a variable of that name in scope it is an error
Such cases are rare, as the variable is only definitely assigned when true for the if (!(o is int i)) throw new ArgumentException("o");
// now i is definitely assigned The proposed scoping rules don't make this legal. On the plus side, they do make this legal: if (o is string s) { /* code using string s */ }
else if (o is Something s) { /* code using Something s */ } |
Just so those of us with a slower uptake understand this. This |
Sorta. If I read this right the variable |
@orthoxerox That's right, there are different boxing/unboxing rules for |
@HaloFour And that's why I questioned allowing universal use of pattern matching's |
@orthoxerox I don't think the point of this issue is to question pattern matching. If you don't see its use, just don't use it :) For more complex cases, it becomes superior IMHO:
VS
And it really shines in a switch construct, as seen in the proposal. |
Might be an argument to use a new keyword like |
I think this could be solved by allowing to match into an existing variable:
(Please don't focus on the chosen syntax ( |
@MrJul I didn't say pattern matching was bad, it's very good where it belongs, inside switches. Your more complex example could be rewritten using my proposal to autocast variables inside if (something is Point)
M(something.X * something.Y);
else
M(12); or actually using switch: switch (something) {
case Point(int x, int y):
M(x, y);
break;
default:
M(12);
break;
}
switch (token) {
case Literal litToken:
return litToken.ParseValue(); //can't use token automatically cast as Literal here
...
}
EDIT: I forgot that the pattern-matching switch has case-level scoping. There's absolutely no need to have |
@MrJul Not a bad idea but I can see how it might be awkward. With the other forms of pattern matching I think it would be a little clearer, the type specific syntax above it would be a little weird. object o = ...;
int x;
if (o is Point(out x, var y)) { } // reuse x, declare y |
I suggest you read the whole specification (#206) for the details, not just the example. Or try the prototype. I'm working on reintegrating the prototype into Roslyn now, so it will be easier to experiment. |
could
|
@alrz Treating |
@gafter in F#, patterns (which do the decomposition) are everywhere, as in |
@alrz If we had started with pattern matching we might have decomposition everywhere too. But we didn't so we don't. |
@gafter can't you just add an |
@whoisj, what would be the gain in doing that? Do you want the tuple or the tuple items? |
@alrz If declared inside the public static implicit operator (TKey,TValue)(KeyValuePair<TKey,TValue> p) => (p.Key,p.Value); However, that still would not cause the |
@gafter Since we are talking about an entirely new feature anyways (pattern matching/decomposition), what would be the best place to talk about the possibilities of decomposition in a |
@gafter since |
@alrz Assignment and decomposition are different. For example, the pattern |
I really dislike the idea the the if branch and the else branch of an if statement do not live in the same scope. In C# 6, the statement
can always legally be transformed into
no matter wat expr, statement1 or statement2 really are. There is elegance in this symmetry. Under the proposed scoping rules, that would no longer be the case. That looks very weird to me. Like many others, I like to test my inputs as soon as possible, throwing an exception when they are wrong, afterwards writing the code processing them when they are ok. So I would like to be able to write something like:
The alternative today works, but requires a redundant cast:
|
@alrz No, tuples are likely to be value types. But since the semantics of the |
@KrisVandermotten We are discussing a decomposition statement, something like
in which the match must be statically verifiable (the compiler can prove it always succeeds), and variables from the pattern are introduced into the enclosing scope. If the match can fail you'd use this form:
In the latter form there is an optional This is perfect for the early error detection cases. Your example would be written let int i = x else throw new Exception("..."); or, if you get paid by the line of code let
int i = x
else
{
throw new Exception("...");
} |
I’m doing a draft reimplementation of pattern matching (#206) now, and trying to figure out the right scoping of pattern variables. I had expected to use whatever we came up with for declaration expressions when those were under development, but it turns out that doesn’t make sense in many situations. For example, given the statement
There is no point in the variable
i
being in scope outside this statement, as it won’t be definitely assigned anywhere else. My current thinking is that a pattern variable introduced in an expression would be limited in scope to the nearest enclosing statement, with the single exception that a pattern variable introduced in the condition of anif
statement would not be in scope in theelse
clause.For a field initializer, the scope would be limited to that declaration. For an expression-bodied property or method, limited to that declaration. For a ctor-initializer, limited to that ctor-initializer. The scope of a pattern variable introduced in a catch clause is the catch block.
For decomposing a tuple (#347), I anticipate introducing a separate statement form, something like
For example
This has the same semantics as the expression
expression is pattern
With the exceptions that
I welcome feedback and comments on this while is it fresh on the drawing board.
@MadsTorgersen @ljw1004 @semihokur @AnthonyDGreen @terrajobst @jaredpar @Pilchie @mattwar @stephentoub @vancem
The text was updated successfully, but these errors were encountered: