Closed gafter closed 6 months ago
Any plan for readonly types(classes and structs)?
should support
readonly i = 0; // shorthand for readonly var
const j = 0; // shorthand for const var
Wasn't val
going to be short for readonly var
?
@jnm2 I just don't like the idea of adding new keyword. Especially it is already have keyword in the language that has the same meaning
readonly
might be a bit longer but we already preserved it from the start. We should reuse it. And to be shorter, just let we use readonly
without var
At least I have seen some suggestion to use let
that would still better than val
because we already have it as keyword, even for linq scope
Especially because val
was not keyword. I really have my code var val = 0;
and I bet there are many people have val
as variable or field name in their code like me. I think val
is a bad choice
@Thaina Yes, I'm inclined to agree.
@Thaina
Especially because val was not keyword. I really have my code var val = 0; and I bet there are many people have val as variable or field name in their code like me. I think val is a bad choice
And you could continue to. Like var
, val
would be a contextual keyword, in that it only behaves like the keyword when it doesn't make sense for it to behave like anything else. So var val = 0;
would remain perfectly legal.
Although I do prefer let
to val
, mostly because I think it looks sufficiently different.
Oh yes! let
was the one I liked. Thanks!
@HaloFour It not breaking change I understand but it still ambiguous
BTW I still don't like let
. I prefer readonly
. But at least let
is better than val
let
is a bit early basic; also was read write. Not sure how let
implies readonly?
@benaadams
let
is a bit early basic; also was read write. Not sure howlet
implies readonly?
let
is also F# where it is the readonly (by default) binding of an identifier. let
is also C# LINQ where it is the declaration of a readonly range variable.
Personally the latter reason is enough for me. It's already a contextual keyword, and in that existing context it creates a readonly identifier.
let is also C# LINQ where it is the declaration of a readonly range variable.
Fair enough π
@gulshan
Any plan for readonly types(classes and structs)?
Do you mean immutable types? If so, that's a completely separate proposal (I'm pretty sure it's been made before).
ITNOA
@Richiban where I can found it?
@soroshsabz
ITNOA
That's a new one!
https://github.com/dotnet/roslyn/issues/7626 and https://github.com/dotnet/roslyn/issues/159 are probably what you're looking for.
Something I like about final
locals in Java is that it's possible to declare one as not assigned and then have branching logic to assign it. The Java compiler uses flow analysis to ensure that for each branch that the local is assigned exactly once.
final String name;
if (entity instanceof Person) {
Person person = (Person)person;
name = person.getFirstName() + " " + person.getLastName();
}
else if (entity instanceof Company) {
Company company = (Company)company;
name = company.getFirmName();
}
This can be useful in those scenarios where you want the local to be readonly, the expression to calculate it can throw and you want the scope of the local to exist beyond a try
block.
@Richiban thanks, but I hope to see comprehensive proposal about immutable object in csharplang project.
@HaloFour Is conditional operator ( ?: ) not sufficient for this purpose?
@soroshsabz
Sometimes not. I amended my comment to mention try
/catch
scenarios where C# offers no single expression. You could extract the logic to a separate function but that's more verbose ceremony. Even if it could be expressed as a single conditional expression sometimes it's more readable expanded out into multiple statements.
Either way, Java supports this, and I make use of it frequently enough that I think it would be useful here.
I think simple rule like "All local readonly
variables must be initializing immediately after declaration." cause to improve simplicity and readability for programmers. In Java case some programmers maybe surprise to final
variable has not initialize and must be track code to find the where is this variable initialize?
@HaloFour,
That's yet another use-case for match
:
let name = entity match (
case Person person : $"{person.FirstName} {person.LastName}",
case Company company : company.FirmName
);
@soroshsabz
We may differ on opinion there. If the expression has to be overly complex in order to satisfy an overly strict language feature that only decreases overall maintainability and readability. I'd rather the flow be logical and the compiler enforce readonly-ness where appropriate.
And as a Java programmer who works directly with hundreds of other Java programmers I can say that this has never been a source of confusion. It's a pattern used fairly frequently across the Java ecosystem. If anything I think I would find it much more annoying that I couldn't declare and assign a readonly
variable like this.
@DavidArno
It's just one exceptionally simple case. match
won't handle the exception handling scenario. And again, forcing the developer to try to pack it all into a one-liner, or to extract that local logic elsewhere, does not improve the readability or maintainability of the program.
In C# we actually have elaborate flow analysis for struct
that it must be set all field properly if we not call the constructor, else it will cause compile error
I agree that readonly
should do the same
I've always liked the idea that readonly
can be applied to local and parameter declarations, e.g.:
public void MyMethod(readonly int arg) //...
and
void Main()
{
readonly string[] items = new [] {"one", "two"};
}
with the added feature that let
can be used a shorthand for readonly var
:
void Main()
{
let items = new [] {"one", "two"};
}
However, you could allow the splitting of declaration and assignment if you write it out in full. The existing definite assignment analysis can be used to make sure that the variable is never overwritten:
void Main()
{
readonly string name;
try {
name = MakeWebRequest("http://my.resource/name");
}
catch (Exception) {
name = null;
}
// Here name is definitely assigned, and now cannot not be written to (Compiler error)
}
But what happens if the variable is perhaps not assigned? Can we still write to it?
void Main()
{
readonly string name;
if(person != null)
{
name = person.Name
}
name = "Bob"; // Is this allowed?
}
@Richiban No you must
if(person != null)
name = person.Name;
else name = "Bob"; // must else
instead
By the way, I was prefer my idea of returnable block #249 instead of using flow analysis as that
local readonly
is one of my reason to make #249 but I forgot to mention it
@Richiban It is unlikely that we would consider adopting something analogous to Java's "blank final" variables or definite unassignment rules. A readonly local would have to be initialized at the point where it is declared.
I understand the desire to support try/catch, but I'm having a hard time envisioning how that would work. Consider:
readonly int x;
try {
DoSomething();
x = 1;
DoSomethingElse();
}
catch {
x = 0;
}
We can see that x
is definitely assigned. However, the value of x
could be assigned twice depending on where the exception occurs.
@Grauenwolf I agree. It makes much more sense that readonly locals insist on being declared and assigned in the same statement, much like var is today.
To be clear, I would prefer that readonly int x
doesn't require immediate assignment. I just don't know if it is possible for this use case.
@Grauenwolf
In Java that code would be a compiler error, specifically because x
isn't definitely assigned exactly once. The following would be legal:
final int x;
try {
x = CalculateX();
}
catch (Throwable e) {
x = 123;
}
The following would not:
final int x;
try {
x = CalculateX();
DoSomethingElse();
}
catch (Throwable e) {
x = 123;
}
Technically that works, but I don't know how I would be able to explain to someone why DoSomethingElse();
makes x=123
illegal.
@Grauenwolf
Java programmers seem to understand it just fine. Java has always been strict about this single assignment rule with final
, even with fields.
Even if it's not added at this time I don't see why it couldn't be considered again in the future. The declaration readonly int x;
would simply be a compiler error now.
At the end of the day it probably won't be used much. So while I would like delayed assignment, I could live without it.
-----Original Message----- From: "HaloFour" notifications@github.com Sent: β4/β7/β2017 3:33 AM To: "dotnet/csharplang" csharplang@noreply.github.com Cc: "Jonathan Allen" grauenwolf@gmail.com; "Mention" mention@noreply.github.com Subject: Re: [dotnet/csharplang] Champion "readonly for locals and parameters"(#188)
@Grauenwolf Java programmers seem to understand it just fine. Java has always been strict about this single assignment rule with final, even with fields. Even if it's not added at this time I don't see why it couldn't be considered again in the future. The declaration readonly int x; would simply be a compiler error now. β You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
Link to the porposal: https://github.com/dotnet/csharplang/blob/master/proposals/readonly_locals.md
"immutable objects" are also related here: https://github.com/dotnet/csharplang/issues/421.
While I like immutable values in F# has anyone considered the legacy code issues here (maybe I missed a comment?)
Specifically the greatest value in this feature will be indicating to the reader that a value is not supposed to be changed. However we have a great body of code where variables were declared mutable so there is no way to know the intent behind these. It makes legacy code misleading because why would variables which are not mutated not declared as such? It seems to me that this issue offsets the benefit that the proposal would provide information about read only variables.
Also note that being explicit here does not provide any new information to the compiler. Java developers often talk about "final or effectively final" the compiler has the full information to know if the variable is changed and therefore can perform any optimizations on readonly variables without the need to declare them explicitly.
It makes legacy code misleading because why would variables which are not mutated not declared as such?
Make a refactoring command to globally change any local variable that's never mutated into an immutable variable. Poof, no more legacy code.
Might even make it an informational warning like we have for member variables that are not readonly but never changed outside of a constructor.
-----Original Message----- From: "Stilgar" notifications@github.com Sent: β6/β3/β2017 3:23 AM To: "dotnet/csharplang" csharplang@noreply.github.com Cc: "Jonathan Allen" grauenwolf@gmail.com; "Mention" mention@noreply.github.com Subject: Re: [dotnet/csharplang] Champion "readonly for locals and parameters"(#188)
While I like immutable values in F# has anyone considered the legacy code issues here (maybe I missed a comment?) Specifically the greatest value in this feature will be indicating to the reader that a value is not supposed to be changed. However we have a great body of code where variables were declared mutable so there is no way to know the intent behind these. It makes legacy code misleading because why would variables which are not mutated not declared as such? It seems to me that this issue offsets the benefit that the proposal would provide information about read only variables. Also note that being explicit here does not provide any new information to the compiler. Java developers often talk about "final or effectively final" the compiler has the full information to know if the variable is changed and therefore can perform any optimizations on readonly variables without the need to declare them explicitly. β You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
Refactoring tool is not a bad solution. Still the syntax should be as short as the mutable syntax or people will fight it instinctively.
Definitely. Thatβs why I like βset x = 5β or βlet x = 5β.
From: Stilgar Sent: Saturday, June 3, 2017 12:01 PM To: dotnet/csharplang Cc: Jonathan Allen; Mention Subject: Re: [dotnet/csharplang] Champion "readonly for locals and parameters"(#188)
Refactoring tool is not a bad solution. Still the syntax should be as short as the mutable syntax or people will fight it instinctively. β You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
The keywords const
and readonly
are all about how to bind to a local or field.
Both share the trait that they can only be assigned to once, at the time they are being declared. However, they are used in different contexts with slightly different semantics.
const
binds a local or field to a value at compile-time. Only valid for values of built-in base types (int, double, string).readonly
binds a field at run-time. Must be assigned directly or in constructor.As this proposed feature only has meaning at compile-time, it seems logical that const
should be the keyword to use. Something unchangeable is intuitively referred to as a "constant".
const
modifies a declaration so that it is only allowed to assign to the name one once, and only when it is being declared.
const Foo x = ...;
Shorter, simplified and probably preferred way with type inference:
const foo = ...
This mirrors var
but with the inability to assign to the variable more than once.
@robertsundstrom
I disagree. The behavior of these parameters/locals much more closely resembles that of readonly
fields. It's true that the behavior is entirely enforced by the compiler and not by the CLR, but the compiler also enforces readonly
fields today as well.
The big differences are that const
members need to be fully evaluated and bound at compile time. The value must be a literal constant (or in a very narrow set of expressions that the compiler is willing to evaluate at compile time). It's not legal to assign them the result of an arbitrary expression, as is the case with readonly
fields. const
members also don't really exist. For fields the compiler will emit a literal
field which serves only as metadata but that field doesn't exist and cannot be directly referenced; the compiler is required to actually use the literal value itself.
These locals and parameters will be bound at runtime to any arbitrary expression. The compiler is only enforcing that the local or parameter cannot be reassigned. With the exception of being allowed to assign/reassign a field in the constructor this behavior closely matches that of readonly
fields. They'll also remain proper local slots and arguments and the IL will continue to reference them directly, also very much like readonly
fields.
Also, const
locals already exist and have very different semantics.
AFAIR local readonly wasn't introduced because scope of method is small enough to not enforce it by compiler. What is changed since we have decided it's not needed?
Just to bikeshed a little bit more about the syntax, in addition to my previously stated reasons for wanting val
TL;DR: the relationships between verbs, nouns, and noun-modifiers, and the flow of grammar. And also recalling that in F#, let
is shorthand for let identifier = value in expression
and is used consistently.
I would like to add that, although there is a precedent in F# to use let
, the reality is that more programmers are likely to be familiar with let
as it is used in ECMAScript where it is the practical analog to C#'s var
.
@aluanhaddad
I've mentioned it already but I think the more important precedent for let
already exists in C#. Why would you use let
to define a readonly variable in one context and val
to define a readonly variable in another context? Why define yet another contextual keyword?
@HaloFour One might just as well ask why, given that query variables are inherently readonly just like foreach
declarators, one would introduce let
as a way to define a readonly local in LINQ expressions when var
could have been reused?
Or for that matter, why introduce a where
clause in LINQ when if
could have been reused?
I believe it was because
from customer in customers
var address = customer.Address
if address.City == "London"
select address.PostCode
quite possibly because it doesn't read as well as
from customer in customers
let address = customer.Address
where address.City == "London"
select address.PostCode
@aluanhaddad
Perhaps. But let
means the same thing in both cases whereas var
is only readonly in that one specific instance.
Apart from that I prefer let
because it visually stands out compared to var
. It's also pronounced quite differently and wouldn't be a homophone in some Asian languages like Japanese (yeah, I'm reaching here).
As a number of people have pointed out, let
reads nicely in local declarations e.g. let message = "Hello world"
, but poorly in foreach statements. Since the loop variables in foreach statements are inherently readonly we've already proposed not allowing the let
keyword but instead sticking with var
. This leaves us in the slightly unsatisfactory position of having the meaning of var
for 'mutable' and let
for 'immutable' except in a foreach statement which has var
for 'immutable' and nothing for 'mutable'.
To solve this confusion I recommend that we... remove the keyword entirely from foreach loops. Controversial, I know! But, hear me out.
Given that this:
var i = 0;
foreach (i in new[] { 1, 2, 3 })
{
}
is currently illegal and specifically gives a compiler error "Type and identifier are both required in a foreach statement", can't we just drop the keyword completely in the foreach declaration? Since the contents of the foreach declaration must contain a variable declaration I don't see the problem. The type of the variable (or var
keyword would become optional), just how it is in Linq:
var _ =
from i in new[] { 1, 2, 3 }
select i;
var _ =
from int i in new[] { 1, 2, 3 }
select i;
So, in my proposal, foreach loops would become:
foreach (i in new[] { 1, 2, 3 })
{
}
This leaves us in the slightly unsatisfactory position of having the meaning of
var
for 'mutable' andlet
for 'immutable' except in a foreach statement which hasvar
for 'immutable' and nothing for 'mutable'.
I agree. It's only slightly unsatisfactory. Iteration variables are immutable to avoid stupid errors, and I don't think anyone minds that. Yes, Captain Hindsight is right that they should've been prefixed with let
for consistency, but he's never around when the decisions are made.
Mutable iteration variables (whenever they are supported) should probably be indicated as ref var
or ref Type
, as this will clearly indicate the expected behavior.
can't we just drop the keyword completely in the foreach declaration?
Yes please. Coming from a VB background where that wasn't necessary, I always found that to be an annoying bit of boilerplate.
-- Jonathan Allen Scholars of Alcala https://www.meetup.com/Scholars-of-Alcala 619-933-8527 <#UNIQUE_IDSafeHtmlFilter>
On Tue, Jul 25, 2017 at 1:42 AM, Richard Gibson notifications@github.com wrote:
As a number of people have pointed out, let reads nicely in local declarations e.g. let message = "Hello world", but poorly in foreach statements. Since the loop variables in foreach statements are inherently readonly we've already proposed not allowing the let keyword but instead sticking with var. This leaves us in the slightly unsatisfactory position of having the meaning of var for 'mutable' and let for 'immutable' except in a foreach statement which has var for 'immutable' and nothing for 'mutable'.
To solve this confusion I recommend that we... remove the keyword entirely from foreach loops. Contraversial, I know! But, hear me out.
Given that this:
var i = 0; foreach (i in new[] { 1, 2, 3 }) { }
is currently illegal and specifically gives a compiler error "Type and identifier are both required in a foreach statement", can't we just drop the keyword completely in the foreach declaration? Since the contents of the foreach declaration must contain a variable declaration I don't see the problem. The type of the variable (or var keyword would become optional), just how it is in Linq:
var = from i in new[] { 1, 2, 3 } select i; var = from int i in new[] { 1, 2, 3 } select i;
So, in my proposal, foreach loops would become:
foreach (i in new[] { 1, 2, 3 }) { }
β You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/csharplang/issues/188#issuecomment-317670653, or mute the thread https://github.com/notifications/unsubscribe-auth/AKIqTdRZzCfNsyMpVa1UNXpm7yz5tNzSks5sRap_gaJpZM4MMfi3 .
Conversely the C# team could always decide to reverse the decision that the iteration variable is immutable in that case. It wouldn't break any existing code to do so. But I am perfectly happy to have the C# language continue to enforce that iteration variables are always immutable but to allow for them to be explicitly declared as readonly
or let
/val
:
foreach (var i in numbers) { ... }
foreach (readonly var in numbers) { ... }
foreach (let i in numbers) { ... }
As for the VB.NET case, variable declarations just work very differently in that language. As the type information is generally a suffix following a specific keyword (Dim
) and an identifier it made sense for type inference to simply permit the omission of that type information. That creates a number of oddities in other parts of the language where the syntax allows for inline declaration of a variable when that type information is provided and a different behavior for when it is not. So now For Each
can have multiple behaviors. Worse, because For Each
could refer to an existing identifier, you could accidentally overwrite another variable or accessible field when your intention was simply to infer the type of a newly declared iteration variable.
C# can at least guard against that specific behavior as foreach
has never permitted reusing an existing identifier as the iteration variable. But I don't think it makes sense to omit the declaration syntax entirely. It simply doesn't read as a declaration without it and could easily be mistaken as a reuse of some existing variable. And frankly, four characters is exceptionally far from an onerous amount of boilerplate.
See also https://github.com/dotnet/roslyn/issues/115
Design review