Proposal: Expression blocks #9242
Replies: 307 comments
-
In terms of impl, this will be a shockingly easy mistake to make (i do it all the time myself). We shoudl def invest in catching this and giving a good message to let people know what the problem is and how to fix it. i.e. if we detect not enough expr args, oing in and seeing if replacing with a semicolon with a comma would fix things and pointing peoplt to that as the problem. |
Beta Was this translation helpful? Give feedback.
-
Is this for ease of impl, or is there a really important reason this doesn't work at the language level? for example, i don't really see any issues with continuing (to a containing loop) midway through one of these block-exprs. |
Beta Was this translation helpful? Give feedback.
-
I also don't see the reasons for any of the restrictions TBH, other than expression trees. |
Beta Was this translation helpful? Give feedback.
-
The evaluation stack may not be empty at the int sum = 0;
foreach (int item in items)
{
sum = sum + { if (item < 3) continue; item };
} |
Beta Was this translation helpful? Give feedback.
-
Riht... but why would i care (as a user)? From a semantics perpective, it just means: throw away everything done so far and go back to the for-loop. I can get that this could be complex in terms of impl. If so, that's fine as a reason. But in terms of hte language/semantics for the user, i dont' really see an issue. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi as a user I find the example by @cston hard to grok. Yanking the whole conditional statement out of the expression block makes everything MUCH clearer. Do you have a counterexample where return, break or continue work better inside an expression block? |
Beta Was this translation helpful? Give feedback.
-
In terms of impl, we should look at the work done in TS here. in TS |
Beta Was this translation helpful? Give feedback.
-
Consider the following:
A block which executes two statements inside, with an empty statement following.
An expression-statement, whose expression is a block expression, with a statement, then the evaluation of 'b'. Would we allow a block to be the expression of an expr-statement? Seems a bit wonky and unhelpful to me (since the value of hte block expression would be thrown away). Should we only allow block expressions in the case where the value will be used? |
Beta Was this translation helpful? Give feedback.
-
@cston To avoid the look-ahead issue, I would suggest an alternative change:
This means that we always parse I think this would solve the look-ahead issue for the compiler, but not so much for humans. I'd still favor |
Beta Was this translation helpful? Give feedback.
-
yes. I'm very on board with a different (lightweight) sigil to indicate clearly that we have an expr block |
Beta Was this translation helpful? Give feedback.
-
How about |
Beta Was this translation helpful? Give feedback.
-
I wonder if the ASP.NET team would lean their preference to |
Beta Was this translation helpful? Give feedback.
-
Isn't that a good reason *not* to use it, then, as it may cause parsing
issues in a Razor/Blazor page?
…On Tue, 7 Jan 2020 at 21:39, Joe4evr ***@***.***> wrote:
I wonder if the ASP.NET team would lean their preference to @{ since
that's already established for a statement block in Razor syntax. 🍝
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#3086?email_source=notifications&email_token=ADIEDQLRWL7SSRWNJ7IZWSDQ4TY73A5CNFSM4KD5XJAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIKL6VY#issuecomment-571785047>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADIEDQKNNDPOSBT2TKGFOMTQ4TY73ANCNFSM4KD5XJAA>
.
|
Beta Was this translation helpful? Give feedback.
-
This is kinda neat but the syntax definitely bothers me as being too subtle of a difference for block vs expression. I think having |
Beta Was this translation helpful? Give feedback.
-
I'm not bothered by the semicolon, but understand the potential confusion. Also, if I undestand correctly, it will not be possible to simply relax the syntax and let the compiler decide whether the block is statement / expression due to lambda type inferrence. Correct? |
Beta Was this translation helpful? Give feedback.
-
What syntax are you suggesting? That C# support IIFE, or that expression blocks are lambda method bodies? |
Beta Was this translation helpful? Give feedback.
-
Kind of both. I look at expression blocks as lambda/method bodies that are immediately invoked. As I see it, an expression block like this: int number = { return 10; } Could be syntactic sugar for this: int AutoGeneratedInlineMethod() { return 10; }
int number = AutoGeneratedInlineMethod(); |
Beta Was this translation helpful? Give feedback.
-
I think that runs into the same issues as the following already means something different: { return 10; } |
Beta Was this translation helpful? Give feedback.
-
I'm not sure what you mean, and maybe I understood it incorrectly. An expression block would be allowed only where some value is expected — that is, as the right operand of an assignment (=), as a parameter to a method, property indexer, constructor, as an operand to some operator, etc. For example: int SomeMethod()
{
{ return 10; } // This is not allowed as an expression block, because the value is not passed to anything. This is the method's return statement, but inside brackets.
}
int MethodWithExpressionBlock()
{
{ return { return 10; }; } // This should be allowed, because the expression block returns 10 as a value to the method's return statement.
} |
Beta Was this translation helpful? Give feedback.
-
That would be a drastic change between statement and expression form, especially since in most (all?) cases in C# expressions don't completely change their nature based on whether or not they are assigned to something. That's my opinion, anyway. My understanding is that at least some of the members of the language team do like the idea of "expression blocks" basically being local functions without the ceremony. |
Beta Was this translation helpful? Give feedback.
-
I understand your point, and I agree with you. However, I'm not exactly suggesting that expressions change their behavior depending on when they are used. What I'm saying is that, other than method calls, using a value in a place where something is not expecting it is already invalid C# anyway. So, you could use expression blocks wherever you could write value literals or lambda functions, for instance. Take a look at the following example: int MyMethod()
{
// This is invalid C#.
"Some string";
// This lambda is also invalid C#. Remember that expression blocks could just be syntactic sugar for this (but immediately invoked).
() => { return 10; };
// This is valid C#, and is not an expression block.
{
Console.WriteLine("This is valid");
}
// Therefore, this is also not an expression block.
{ return 10; }
} |
Beta Was this translation helpful? Give feedback.
-
What is the meaning of:
And how do I return from the method inside the expression? |
Beta Was this translation helpful? Give feedback.
-
I have been propose var i = :{
if (DateTime.Today.DayOfWeek == DayOfWeek.Sunday)
return 5;
return 6;
} Even And 249 state. What we really want is anonymous immediate local function. We want local function that would be anonymous and just execute immediately |
Beta Was this translation helpful? Give feedback.
-
@ayende |
Beta Was this translation helpful? Give feedback.
-
Another way I think is good for implementing this, but without the confusion, would be to just allow a lambda to be called immediately after declaration, returning its value, like others have mentioned. Like this: int i = () => { return 10; }(); Because the lambda would be invoked, the lambda itself would never be used as a value, and thus it would never need to capture variables and would not need closures. This still has the limitation of not being possible to access the value of the previous expression. However, to solve this, a new operator could be introduced to access the current expression value, similar to what you can do with the string words = "Hello world!"
.Replace("world", "universe")
is string s ? ((Func<string, string>)((s) =>
{
for (int i = 0; i < 10; i++)
{
s += "[" + i + "]";
}
return s;
}))(s) : ""; In the previous example, I'm using a combination of the ternary logical operator and the string words = GetWords() s s.Replace("world", universe); The previous example, shows a roughly thought way for assigning the value of an expression to a variable created inline. This is similar to string s = GetWords();
string words = s.Replace("world", universe); With a way to access the value of the previous expression, like in the previous example, and being able to immediately invoke a lambda, we can turn this: string words = "Hello world!"
.Replace("world", "universe")
is string s ? ((Func<string, string>)((s) =>
{
for (int i = 0; i < 10; i++)
{
s += "[" + i + "]";
}
return s;
}))(s) : ""; Into this: string words = "Hello world!"
.Replace("world", "universe")
s () =>
{
for (int i = 0; i < 10; i++)
{
s += "[" + i + "]";
}
return s;
}(); Because the lambda is immediately executed, 's' would not be captured by the lambda, ensured by the compiler. In this case, the lambda is an With this approach, there would be no expression blocks whatsoever. There would be only immediately invoked lambdas and an "expression identifier" operator. Expression blocks would be just a side effect. This approach would be an alternative way for avoiding the confusion with the expectations people have with how to return from an expression block and things like that. This way, people would be working with the familiarity of lambdas. Also, the "expression identifier" operator would be very useful for other things as well. |
Beta Was this translation helpful? Give feedback.
-
See: #4748 |
Beta Was this translation helpful? Give feedback.
-
Thinking expression blocks of a local method or a lambda seems a roundabout way to me. Using local method or lamda is just a workaround of current C# not having expression blocks. With expression blocks, such redundant method calls would be unnecessary. From my point of view, expression block is a syntax that allows statements to be executed during expression evaluation. No method call is involved. (Expression block syntax could exist even in a hypothetical language that doesn't support any functions.) Re-defining |
Beta Was this translation helpful? Give feedback.
-
For me personally, the expressions blocks were a way to keep DRY and also work around not having the cascade operator (granted I'm open to that being an alternative as well.) For reference see the cascade operator in flutter: Example:
But I really like the "{}" block notation. It's technically more dry by not having to repeat the ".." over and over, granted not as much overhead DRY reduction as not having to repeat the variable name, but still a net win! The cascade operator might be more readable in some cases, but overall I think the curly brackets for dot notation is the best. |
Beta Was this translation helpful? Give feedback.
-
That looks like a separate concern from expression blocks to me. I think a forward pipe operator could handle both of those cases. #96 and #74. Just pipe the result into the expression. var paint = Paint() |>
${
@.color = Colors.black
@.strokeCap = StrokeCap.round
@.strokeWidth = 5.0;
yield @;
} Although, that could get complicated with nested pipes... Maybe it's not such a great idea after all. Maybe it would be ok if the pipe operator would let you name the piped value |
Beta Was this translation helpful? Give feedback.
-
I don't think expression blocks as proposed/discussed here are related to cascade operators, initializers or the like. You're not starting from a value and composing over that value. At most you're working with the identifiers already in scope. Any statements within the block itself would be self-contained, like a statement block or a local function with no arguments. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Champion issue: #9243
Proposal
Allow a block of statements with a trailing expression as an expression.
Syntax
Examples:
Execution
An expression block is executed by transferring control to the first statement.
When and if control reaches the end of a statement, control is transferred to the next statement.
When and if control reaches the end of the last statement, the trailing expression is evaluated and the result left on the evaluation stack.
The evaluation stack may not be empty at the beginning of the expression block so control cannot enter the block other than at the first statement.
Control cannot leave the block other than after the trailing expression unless an exception is thrown executing the statements or the expression.
Restrictions
return
,yield break
,yield return
are not allowed in the expression block statements.break
andcontinue
may be used only in nested loops orswitch
statements.goto
may be used to jump to other statements within the expression block but not to statements outside the block.out
variable declarations in the statements or expression are scoped to the expression block.using expr;
may be used in the statements. The implicittry
/finally
surrounds the remaining statements and the trailing expression soDispose()
is invoked after evaluating the trailing expression.Expression trees cannot contain block expressions.
See also
Proposal: Sequence Expressions #377
LDM 2020-01-22
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md#discriminated-unions
https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-28.md#block-bodied-switch-expression-arms
Beta Was this translation helpful? Give feedback.
All reactions