Replies
47 comments
THe use cases are very uncompelling to me :) The last one, especially, feels particularly like the wrong solution to the problem as stated. When i see someone say they had to write: var (x,y) = GetMultipleReturnValues();
var result = DoSomethingWithThoseValues(x, y); And they'd like it as a single expression, i'd want that solution to be: var result = DoSomethingWithThoseValues(GetMultipleReturnValues()); // or maybe
var result = DoSomethingWithThoseValues(GetMultipleReturnValues()...); // ... to indicate splatting (perhaps with some minor ceremony), and definitely not: (var (x,y) = GetMultipleReturnValues(); DoSomethingWithThoseValue(x, y)) :) |
That said, let's not dive into the weeds on splatting. I don't want to derail. I just want to point out that if there are to be examples, i want them to resonate with me. :) |
This is a good example of how as patterns could be used: while ((GetNextChar() as var ch) == 'a' || ch == 'b' || ch == 'c')
{
// use ch here
} |
@DavidArno as patterns may have problems working with value types. But it is similar. |
@orthoxerox yes, but with only a very limited sequence. |
@DavidArno afaik that's not an as pattern, it's useful to name the whole pattern, like |
@alrz, @mattwar, |
Just realised what you did there. A proper "as pattern" could be really useful in pattern matching. For switch (x)
{
case (1, var a) as t when MeetsCondition(a):
HandleTuple(t);
break;
...
} |
for that one, I'd prefer an "or" pattern (#118), while (GetNextChar() is ('a' | 'b' | 'c')) or something like that.
As an alternative to splatting or sequence expressions for this case, the "case expression" might be interesting too (present in the long-outdated pattern spec), var result = GetMultipleReturnValues() case var (x, y): DoSomethingWithThoseValue(x, y); However, splatting is the obvious solution for this specific example. |
@DavidArno Yeah, this is a little off-topic though but you probably want to scratch as as the chosen token for that since it has the same precedence as is so e is p as t is already a valid syntax. |
Following on with the idea of substitution If I had the functions: int F(int a, int b);
int G(int c); and wanted to compose an expression that did this: var p = GetPoint();
var r = F(G(p.X), p.Y); I could write it as: F(G((var p = GetPoint()).X), p.Y) because order of evaluation works in my favor. but if I wanted to write: var p = GetPoint();
var r = F(p.X, G(p.Y));
|
@alrz I agree that it the extended declaration expression works and probably is more clear. However, I was going through the process of thinking through the alternative because my original argument for the extended declaration expression claimed that it was not possible to express it any other way, but I disproved that. It is possible using the basic declaration expression alone and inlining it in any expression, or even using the is operator and the ternary. But I'd still rather not use the alternatives. |
Or even more concisely: using static LanguageExt.Prelude;
var result = from x in Some(CalculateX())
where x % 5 == 0
from y in Some(CalculateY())
where y % 3 == 0
select x + y; But to the point of this discussion. F# allows declarations of variables (values) in expressions, because this: let x = 10
let y = 20
x + y Can be translated to C# as: ((int x) =>
((int y) =>
x + y)(20))(10); That won't compile, but you get the idea. So I think if we accept that an inline declaration creates a scope over the rest of the expression which doesn't abuse the sanctity of the expression, then this would solve an enormous number of problems with trying to write code in an expression oriented style in C#. May I suggest that I think this only works inline if you consider the expression like the lambda above. So: (int x, int y) MovePoint(int amount) =>
let (x, y) = GetPoint(),
(x + amount, y + amount); That allows expressions to pre-load variables so they don't get fetched multiple times like so: (int x, int y) MovePoint(int amount) =>
(GetPoint().x + amount, GetPoint().y + amount); This ^^ is definitely one of the big pain points for me with expression oriented coding. Things like this (from the original proposal) though I think are hard to parse and understand: char ch;
while ((ch = GetNextChar()) == 'a' || ch == 'b' || ch == 'c')
{
} In that situation you'd want: while ( let ch = GetNextChar(), ch == 'a' || ch == 'b' || ch == 'c' )
{
} I'm using a comma for a separator. But I'm not particularly attached to it. |
10 years later... Microsoft writes entire .net standard 5 library in one statement |
Just the other day I wanted to do a let binding in an expression to partially eval a calculation public static CurriedFunction F =
(int x) =>
let y = expensive(x)
z => notSoExpensive(y,z) so I also, like @louthy above, would like let-expressions out side linq |
Lambda's allow for full statement bodies unless you are really using Expression.
And if you are writing a method body, just use a full body:
But I think there are other cases where declaration expressions are helpful. |
Honestly, I don't like it. Mixing assignment and reading of the same variable is already messy enough. Adding declarations to the mix makes it even harder to read and the examples I've seen are not very compelling. It made sense with pattern matching and LINQ because they would be unusable otherwise. But to open the floodgate by allowing declarations to literally appear anywhere is too much. |
A couple of extension methods allow use of the existing out var pattern to simulate Let/As:
For Then you can have
Or with
Though it does require use of the Having declarations be expressions would allow something like
be reduced to
or possibly
|
I have often suggested overloading double-colon for this ( if (validating && GetPerson(form.FullName)::person.Age >= 18) {
if (!form.HasSignatureFrom(person))
errors.Write(person, "Persons 18 or older must sign the form.");
} The advantage of this syntax is that it often requires no additional parentheses. A problem with allowing variable declarations inside expressions (as C# already does with |
Now that 7.0 is out and C# has introduced out variables and type patterns (with variable declaration), its time to consider a general purpose declaration expression that can introduce variables within expressions.
A declaration expression is fundamentally an initialized variable declaration as an expression instead of a statement.
Basic Declaration Expression
But you'll probably need parentheses to disambiguate within most expressions. The value of the declaration expression is the value of variable after it is declared. This is basically the same as an assignment expression, except you are declaring the variable too.
Today you can write code like this with assignment expressions, and in certain code bases it may be a familiar pattern.
This is okay, but requires you to declare the variable in the outer statement context, and while that doesn't appear to be too onerous given the example, its certainly possible that such a statement scope might not even be available, such as in a field initializer, or base class invocation expression.
With a declaration expression you would be able to also declare the variable within the expression. It would have the same visibility as a variable declared by an out variable or an is pattern.
Extended Declaration Expressions
When you have an is pattern variable used in an expression you get to refer to the variable within the rest of expression because the is expression is a boolean expression and you have a natural way to extend boolean expressions with the '&&' and '||' operators.
Out variables used in expressions are typically the same because they are often used in methods following the Try pattern, so they too return bool and you typically get to refer to these declared variables as a part of a continuation of that boolean expression.
In the example for the declaration expression above, the declared variable was also used later in the expression because the overall expression was turned into a boolean expression by using '=='.
But that might not be the only reason to need a variable assignment within an expression. For example, I may want to calculate a value and then use parts of it in a follow on expression. In order to do this we need the ability to compute a follow on expression where the declared value is visible where we don't currently have an operator to do that.
For example, if I had a statement context, I might want to write code that fetches a value or instance from one method and use that value or instance more than once in a separate expression.
But this would be impossible to write as a single expression, unless we could have another expression follow on from the first using some other operator.
Introducing an extended declaration expression.
An extended declaration expression follows the declaration with a semicolon (or to be determined better punctuation) and another expression that can be evaluated with references to the declared variable. The value of the extended declaration expression is the value of the follow on expression.
Now you can write:
If you need to.
Updated example to use arbitrary type with members and addition instead of invocation to avoid confusion with a potential tuple splat operator.