Replies: 47 comments 2 replies
-
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)) :) |
Beta Was this translation helpful? Give feedback.
-
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. :) |
Beta Was this translation helpful? Give feedback.
-
Sure, maybe I need some more compelling example. Splatting might work for the explicit example I used, but not generally. |
Beta Was this translation helpful? Give feedback.
-
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
} |
Beta Was this translation helpful? Give feedback.
-
To avoid splatting comparison, instead I get multiple values from one source and then operate on them in an non-spat way, ... like addition. (var p = GetPoint(); p.X + p.Y) |
Beta Was this translation helpful? Give feedback.
-
@DavidArno as patterns may have problems working with value types. But it is similar. |
Beta Was this translation helpful? Give feedback.
-
@mattwar your last example looks suspiciously like a sequence expression (#72) |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox yes, but with only a very limited sequence. |
Beta Was this translation helpful? Give feedback.
-
After thinking about it, there is a way to do this already. (GetPoint() is var p ? p.X + p.Y : default(int)) This works, but is a bit ugly, since I have to use the ternary and have to supply the false case that never executes. |
Beta Was this translation helpful? Give feedback.
-
@DavidArno afaik that's not an as pattern, it's useful to name the whole pattern, like |
Beta Was this translation helpful? Give feedback.
-
@alrz, @mattwar, |
Beta Was this translation helpful? Give feedback.
-
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;
...
} |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
I updated the example for extended declaration expression to not use tuple and then immediate invocation to avoid confusion with a possible tuple splat operator. |
Beta Was this translation helpful? Give feedback.
-
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));
|
Beta Was this translation helpful? Give feedback.
-
Nope. I'm wrong.. (sigh) F((var p = GetPoint()).X, G(p.Y)) |
Beta Was this translation helpful? Give feedback.
-
Apparently, I can have this discussions all by my self. |
Beta Was this translation helpful? Give feedback.
-
Don't worry, we will be your silent rubber ducks. |
Beta Was this translation helpful? Give feedback.
-
I'm good at contradicting myself and arguing against what I just argued for. Makes it more exciting. |
Beta Was this translation helpful? Give feedback.
-
Could be I think it would make sense to restrict declaration expressions to not appear everywhere. |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
10 years later... Microsoft writes entire .net standard 5 library in one statement |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Can we also include here the following use case?
|
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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
|
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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.
Beta Was this translation helpful? Give feedback.
All reactions