Champion "Declaration Expressions" #8935
Replies: 117 comments
-
With the advent of pattern-matching, people now occasionally write code such as if (expr is var x && M(x.a, x.b)) Which seems like an abuse of pattern-matching. If we add declaration expressions, this could be written as if (M((var x = expr).a, x.b) The latter form has the advantage that it works in non-boolean contexts as well. If we add sequence expressions (which is not proposed herein), this could be written if ((var x = expr; M(x.a, x.b))) or possibly if (var x = expr; M(x.a, x.b)) |
Beta Was this translation helpful? Give feedback.
-
Would nicely answer #400. |
Beta Was this translation helpful? Give feedback.
-
It's good to see this being considered. I still think using if (M((expr as var x).a, x.b) #402 looks at as-based declaration expressions in more detail. However, |
Beta Was this translation helpful? Give feedback.
-
would it also cause conditional declaration when used in conditional debug methods?
will this declare variable |
Beta Was this translation helpful? Give feedback.
-
No, but it would be possible to use Debug.Assert(var c = true);
Console.Write(c); // ERROR in release mode but compiles fine in debug |
Beta Was this translation helpful? Give feedback.
-
@alrz That kind of thing isn't necessarily new either: bool c;
Debug.Assert(c = true);
Console.Write(c); // ERROR in release mode but compiles fine in debug |
Beta Was this translation helpful? Give feedback.
-
@jnm2 Exactly. |
Beta Was this translation helpful? Give feedback.
-
I think that declaration expressions would be easier to use if they didn't require so much typing. For example:
This would also have the advantage that existing code would only have to be slighly refactored to take advantage of this. The syntax makes me thing of SQL where you can quickly provide a new name for a table:
|
Beta Was this translation helpful? Give feedback.
-
That very much looks like you're declaring a variable called 'CeoName' of type 'Company.Ceo.FullName' fwiw. |
Beta Was this translation helpful? Give feedback.
-
I like the sound of this but I feel it would benefit more from C++ like behaviour with the way it handles the scope of variables, C# has this mechanism in for loops too. At the very least the scoping of C# variables needs to be made consistent, and I would recommend that the blocks of for, while, if, etc are capable of defining scope for the variable, see below.
If this pattern was adopted to begin with as well as declaration expressions, then out variables would not have been needed for conditional statements as the variable can be defined, then used for the condition, and the variable declaration is limited by scope so the issue demonstrated with Debug.Asser() could not arise as you'd receive compiler errors on both release and debug builds. The example here is quite poor but this kind of mechanism would work well for TryParse.
|
Beta Was this translation helpful? Give feedback.
-
@SatsumaBenji Restricting the scope of
I don't think the inconsistencies between the different kinds of statements are actually an issue. |
Beta Was this translation helpful? Give feedback.
-
private readonly Dictionary<string, Program> _cache
= new Dictionary<string, Program>();
public Program GetProgram(string name)
{
if (!_cache.Contains(name))
{
_cache[name] = new Program();
}
return _cache[name];
} No But variables do now leak in C#. That ship has sailed. |
Beta Was this translation helpful? Give feedback.
-
@DavidArno That's two or three times the hash table lookups depending on the frequency of misses. The first code is preferable to me– even better behind a GetOrAdd extension method. |
Beta Was this translation helpful? Give feedback.
-
Different approaches to coding, I guess. I far prefer to suffer the potential performance hit in order to keep the code simple. Only if perf analysis shows it to be a problem would I then degrade the code to improve performance. |
Beta Was this translation helpful? Give feedback.
-
I already dealt with that. No degrading necessary– put it behind a |
Beta Was this translation helpful? Give feedback.
-
Yes. Because they do. Here, i'll show you the compiled code and you tell me if it was statements or expressions that generated it:
Which of these was written with expression form, and which was written with statement form? |
Beta Was this translation helpful? Give feedback.
-
That's not sufficient. We would have to define exactly when they would be evaluated wrt to all the expressions in the expression. Generally speaking, following the rule we have for virtually everything in the language, it would be left-to-right evaluation. |
Beta Was this translation helpful? Give feedback.
-
They are evaluated exactly as they would be were they in-lined, like I said. This is well defined and consistent. |
Beta Was this translation helpful? Give feedback.
-
I have no idea what the Lazy point is then... why would it be lazy... if it would just be evaluated in its left-to-right ordering wrt to the rest of the code? Furthermore, how does this support your idea that by having an expression form this now provides the special abilities to then have different evaluation strategies. None of that holds. We would specify out explicitly exactly the evaluation strategy, and it would only execute in that fashion. |
Beta Was this translation helpful? Give feedback.
-
Fundamentally, to me you've made several claims about the C# language that simply aren't correct. They seem to be a perception about how you think it operates which is very distant from how it actually operates and which is spelled out in detail in the specification. I'm trying to get this disconnect reconciled as it's being used to make further claims about how this hypothetical feature might work. And those claims just do not jive with any sort of approach that has any reasonable chance of happening. e.g. version after version we have never gone that direction, opting to keep our very imperative, strictly ordered, view on what code means. Heck, when it came to the one feature that really might have taken on thse semantics (Linq), we explicitly choose to define it the exact opposite way. Linq explicitly is not defined as a system whereby things are free to reorder evaluation. Instead, it's literally defined as a syntactic transform to imperative code such the order and meaning is exactly the same as writing it in that imperative version. Indeed, internally, the original impl worked by literally making that exact AST transform to align directly with the spec in that regard. This was not an internal thing done just to get linq working. It was strictly the definition of how it had to evaluate, even though it shares similarities to non-imperative query languages which are free to do things entirely differently. From a static semantics perspective, our language takes the imperative view. |
Beta Was this translation helpful? Give feedback.
-
So your argument seems to be that because an expression can be decomposed into instructions, they are therefore equivalent. This is the classical reductionist fallacy. You are made of atoms, therefore you are nothing but atoms? The specific arrangement of a decomposed thing is not nothing. That's information there. There are layers of logic systems at play, and just because there are isomorphic relationships between them does not mean they are the same. Taking a trivial example of where the JITter optimizes out the variable declarations and showing the asm rather than the IL, does not make a counter case I'm afraid. I think you're conflating two things: 1) the concrete suggestion I have for this feature; and 2) the various examples and explanations I've been attempting to make in support of increased expressiveness in general. You're making strawman arguments I'm simply not making. I'm not advocating for some kind of out of order execution at the level of the C# language. It's also not fair to claim that I've made several claims about the language that aren't true. I was incorrect about the scope of the variable that leaks out of I still maintain that expressions represent a higher level of abstraction, regardless of how it's formally defined in the language, and I think you're confusing the ability to translate between them as equivalence. This is actually a commonly misunderstood principle because programmers tend to be fixated on implementation and are not usually taught much formal logic. To illustrate, let me ask the question, is Now in terms of C#, I understand that like most imperative languages, order of evaluation is specified in the language, and I further realize that the language does very little in terms of optimization, though it does participate. But again, imperative style code will in general produce different IL, and in non trivial cases, that leads to different execution; or are you asserting that were we to declare a variable for every term within complex, real-world code, the runtime will always eliminate those variables, loops, and such and produce exactly the same code? All of this ignores the stylistic differences that expressiveness encourages, which will also produce different code at the human level. Have you read any of the research of axiomatic complexity and imperative style and the correlation to bugs? One more thing, you keep using the term "our" to refer to C# rather than referring to it in the 3rd person like most people do. May I ask why you use this verbiage and who you are including and/or excluding? This is taking too much time, so I'll have to participate less here, but I hope that others more capable than I can champion this feature. Also, here is the definition of expression as found in the C# language spec:
|
Beta Was this translation helpful? Give feedback.
-
No, i'm saying they're equivalent because we explicitly define them to be equivalent. Our language specification describes the meaning of this with imperative semantics. It explicitly says how you get things, not just what you get.
Myself and other members of the design group and implementation team.
I have no idea what htis means. 'expression' is a defined concept with meaning in the context of the C# and the language features we're discussing here. Language proposals and ideas are discussed and designed within that context. You seem to be conflating how other languages and systems may work with expressions (which certainly may be different from C#), but that has no bearing here as this is entirely about C# and the design of a potential 'declaration expression' feature, which would certainly be designed entirely within the views of C# how these things should work.
In the context of C#, it is imperative. We define strictly what this needs to produce and how it needs to produce it.
That's really not the point of our language at all, and it's not how we go about the design of it. Indeed, i'd strongly say we approach this from the reverse direction. Strictly discussing what the operation does, and exactly how it does it. The goal then though is to make what is normally a large set of manual steps much more pleasant through ergonomic syntax. You can see that sort of process take form in the work for things coming in C#12 (like collection literals). There we are defining a new expression, but entirely defining it in terms of exact imperative semantics). Note: this is entirely different from approaches taken with other languages we've worked on in other domains that view this sort of thing differently. I have plenty of experience with language/compiler work coming much more from the direction you are talking about. I did much of my master's work in that space, heavily focusing on compiler systems that could translate heavily precisely because they were supporting languages that did not describe the 'how', but the 'what'. But that's not C#, and the design and discussions of our features are entirely with the context of that.
Important part italicized. The language defines that order. It is not permissible for the compiler to do things like "choose early vs. late ... no evaluations". If it did something early or late, it would not be abiding by the strict order of evaluation specified. Similarly, if it did 'no evaluations' that would also violate the strict ordering that is required. |
Beta Was this translation helpful? Give feedback.
-
// ...
It cannot choose these things. It must only do what the language explicitly says must happen. |
Beta Was this translation helpful? Give feedback.
-
That's the point of this proposal, though: there are situations where declaring these variables upfront makes the code less readable. My scenario, building a UI tree, is a perfect example of this: if you have a large init block with many controls/elements, moving these control creation LOCs away from their usage is a significant downgrade. Compare it to HTML, the old ASPX, XAML, etc., where the control is defined and used in the same place - that's what we want to achieve here. <div>
<label for="tbName">Name</label>
<input id="tbName" />
</div> this.Controls.Add(new Div
{
(var lblName = new Label { Text = "Name" }),
new TextBox { Id = "tbName" }.Init(x => lblName.For = x),
});
It's not just the definitions of a local; I want to see the UI tree with all the properties, events, etc. in its context. |
Beta Was this translation helpful? Give feedback.
-
IMO the situation with |
Beta Was this translation helpful? Give feedback.
-
It's not a circular reference, rather a one-way reference. And quite on the contrary, these situations are extremely common: in addition to labels, think validators, and multi-step choices - one dropdown filters the list of choices in another dropdown, etc. This applies to both desktop and web UI scenarios. |
Beta Was this translation helpful? Give feedback.
-
Sorry, it's a forward reference. But even with local declarations this is not a solved problem given that you need the latter control for the initializer of the former control, necessitating this other extension method. These frameworks were never designed to be initialized in one giant initializer expression like this and I think there will always be friction points trying to use them as such. |
Beta Was this translation helpful? Give feedback.
-
I really doubt it. Once we have compound assignments, mixed props/collections, and expression locals, that's pretty much it. I know, because I'm using it already. |
Beta Was this translation helpful? Give feedback.
-
Why can't it just be this? 0 new language features needed this.Controls.Add(new Div()
{
new Label() { Text = "Name" }.Self(out var lblName),
new TextBox() { Id = "Name" }.Init(x => lblName.For = x.Id)
}); |
Beta Was this translation helpful? Give feedback.
-
That's what I'm using now. However, I think this would be yet easier with a language feature, especially given others have an interest for other scenarios, as well. |
Beta Was this translation helpful? Give feedback.
-
See #595, the section entitled "Basic Declaration Expression".
For related but separate ideas (sequence expressions), see #595, section entitled "Extended Declaration Expressions", and #377 (though the scoping proposed in #377 would have to be revisited in light of language changes since the time it was written).
Declaration expressions were tentatively added in C# 6, but removed while we worked out details of the scoping and syntax error recovery. Since we added pattern-matching and out variables, the scoping issues have been thoroughly solved.
/cc @cston
Beta Was this translation helpful? Give feedback.
All reactions