Champion "readonly for locals and parameters" #188

Open
gafter opened this Issue Feb 26, 2017 · 182 comments

Comments

@gafter
Member

gafter commented Feb 26, 2017

  • Proposal added
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

See also dotnet/roslyn#115

@gulshan

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan Mar 23, 2017

Any plan for readonly types(classes and structs)?

gulshan commented Mar 23, 2017

Any plan for readonly types(classes and structs)?

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 27, 2017

should support

readonly i = 0; // shorthand for readonly var
const j = 0; // shorthand for const var

Thaina commented Mar 27, 2017

should support

readonly i = 0; // shorthand for readonly var
const j = 0; // shorthand for const var
@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Mar 27, 2017

Contributor

Wasn't val going to be short for readonly var?

Contributor

jnm2 commented Mar 27, 2017

Wasn't val going to be short for readonly var?

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 28, 2017

@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 commented Mar 28, 2017

@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

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Mar 28, 2017

Contributor

@Thaina Yes, I'm inclined to agree.

Contributor

jnm2 commented Mar 28, 2017

@Thaina Yes, I'm inclined to agree.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 28, 2017

Contributor

@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.

Contributor

HaloFour commented Mar 28, 2017

@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.

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Mar 28, 2017

Contributor

Oh yes! let was the one I liked. Thanks!

Contributor

jnm2 commented Mar 28, 2017

Oh yes! let was the one I liked. Thanks!

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 28, 2017

@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

Thaina commented Mar 28, 2017

@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

@benaadams

This comment has been minimized.

Show comment
Hide comment
@benaadams

benaadams Mar 28, 2017

let is a bit early basic; also was read write. Not sure how let implies readonly?

let is a bit early basic; also was read write. Not sure how let implies readonly?

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 28, 2017

Contributor

@benaadams

let is a bit early basic; also was read write. Not sure how let 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.

Contributor

HaloFour commented Mar 28, 2017

@benaadams

let is a bit early basic; also was read write. Not sure how let 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.

@benaadams

This comment has been minimized.

Show comment
Hide comment
@benaadams

benaadams Mar 28, 2017

let is also C# LINQ where it is the declaration of a readonly range variable.

Fair enough 😄

let is also C# LINQ where it is the declaration of a readonly range variable.

Fair enough 😄

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Mar 30, 2017

@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).

@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).

@soroshsabz

This comment has been minimized.

Show comment
Hide comment
@soroshsabz

soroshsabz Mar 30, 2017

ITNOA

@Richiban where I can found it?

ITNOA

@Richiban where I can found it?

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Mar 30, 2017

@soroshsabz

ITNOA

That's a new one!

dotnet/roslyn#7626 and dotnet/roslyn#159 are probably what you're looking for.

@soroshsabz

ITNOA

That's a new one!

dotnet/roslyn#7626 and dotnet/roslyn#159 are probably what you're looking for.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 30, 2017

Contributor

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.

Contributor

HaloFour commented Mar 30, 2017

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.

@soroshsabz

This comment has been minimized.

Show comment
Hide comment
@soroshsabz

soroshsabz Mar 30, 2017

@Richiban thanks, but I hope to see comprehensive proposal about immutable object in csharplang project.

soroshsabz commented Mar 30, 2017

@Richiban thanks, but I hope to see comprehensive proposal about immutable object in csharplang project.

@soroshsabz

This comment has been minimized.

Show comment
Hide comment
@soroshsabz

soroshsabz Mar 30, 2017

@HaloFour Is conditional operator ( ?: ) not sufficient for this purpose?

@HaloFour Is conditional operator ( ?: ) not sufficient for this purpose?

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 30, 2017

Contributor

@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.

Contributor

HaloFour commented Mar 30, 2017

@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.

@soroshsabz

This comment has been minimized.

Show comment
Hide comment
@soroshsabz

soroshsabz Mar 30, 2017

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?

soroshsabz commented Mar 30, 2017

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?

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 30, 2017

@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
);

@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
);
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 30, 2017

Contributor

@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.

Contributor

HaloFour commented Mar 30, 2017

@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.

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 30, 2017

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

Thaina commented Mar 30, 2017

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

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Mar 30, 2017

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?
}

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?
}
@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 30, 2017

@Richiban No you must

if(person != null)
    name = person.Name;
else name = "Bob"; // must else

instead

Thaina commented Mar 30, 2017

@Richiban No you must

if(person != null)
    name = person.Name;
else name = "Bob"; // must else

instead

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 30, 2017

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

Thaina commented Mar 30, 2017

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

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Apr 4, 2017

Member

@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.

Member

gafter commented Apr 4, 2017

@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.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Apr 7, 2017

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 commented Apr 7, 2017

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.

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Apr 7, 2017

@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.

Richiban commented Apr 7, 2017

@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.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Apr 7, 2017

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.

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.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Apr 7, 2017

Contributor

@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;
}
Contributor

HaloFour commented Apr 7, 2017

@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;
}
@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Feb 21, 2018

@knocte variable could means it can vary when it initailize. It did not need to means that it could vary after that

Consider mathematic variable

x + y = 2
x - y = 2

At first it might look like it could be any integer in the world. But when we write 2 equation then both variable was lock down to only one solution. It can't be vary anymore if we want the solution. It vary by the means that we can put anything in it place. But it not vary by the value of it

Thaina commented Feb 21, 2018

@knocte variable could means it can vary when it initailize. It did not need to means that it could vary after that

Consider mathematic variable

x + y = 2
x - y = 2

At first it might look like it could be any integer in the world. But when we write 2 equation then both variable was lock down to only one solution. It can't be vary anymore if we want the solution. It vary by the means that we can put anything in it place. But it not vary by the value of it

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Feb 21, 2018

Member

“Variable” means that it may vary from one instance of the variable to another, not that it is mutable.

Member

gafter commented Feb 21, 2018

“Variable” means that it may vary from one instance of the variable to another, not that it is mutable.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 21, 2018

@gafter & @Thaina, really? I think there's an element of desperation to those arguments there 😆.

And anyway, who cares what var means: just use let instead and the whole "well readonly var is an oxymoron, actually!" pedantic argument is neatly sidestepped.

DavidArno commented Feb 21, 2018

@gafter & @Thaina, really? I think there's an element of desperation to those arguments there 😆.

And anyway, who cares what var means: just use let instead and the whole "well readonly var is an oxymoron, actually!" pedantic argument is neatly sidestepped.

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Feb 21, 2018

Contributor

Why do you think that definition is desperate?

Contributor

jnm2 commented Feb 21, 2018

Why do you think that definition is desperate?

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 21, 2018

@DavidArno let works very well replacing readonly var for local variables. But @alrz examples really made question if it work well for parameters. Adding let/readonly to parameters everywhere doesn't sound fun and most parameters aren't mutated.

wanton7 commented Feb 21, 2018

@DavidArno let works very well replacing readonly var for local variables. But @alrz examples really made question if it work well for parameters. Adding let/readonly to parameters everywhere doesn't sound fun and most parameters aren't mutated.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 21, 2018

@wanton7, no let would not work for parameters. readonly does work nicely. I can't think of a situation where you'd have readonly var in a parameter though.

@wanton7, no let would not work for parameters. readonly does work nicely. I can't think of a situation where you'd have readonly var in a parameter though.

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 21, 2018

@DavidArno only in out var but out let works there just fine. But because most parameters aren't mutated you would want almost all of them readonly. Then writing readonly everywhere like this for lamba (readonly x) => x + 7 becomes very tedious

wanton7 commented Feb 21, 2018

@DavidArno only in out var but out let works there just fine. But because most parameters aren't mutated you would want almost all of them readonly. Then writing readonly everywhere like this for lamba (readonly x) => x + 7 becomes very tedious

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Feb 21, 2018

Contributor

@DavidArno I'm suggesting using let all the way, for parameters and locals,

void M(in int p0, out int p1, ref int p2, let int p3) {}

Plus other examples above.

Contributor

alrz commented Feb 21, 2018

@DavidArno I'm suggesting using let all the way, for parameters and locals,

void M(in int p0, out int p1, ref int p2, let int p3) {}

Plus other examples above.

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 21, 2018

@alrz even (let x) => x +7 feels tedious if you have to write it everywhere. Maybe this could be split to two proposals? I think let as readonly var is a no brainer, but I think there should be project wide setting to default to readonly parameters. That would need new keyword like writable when there is rare occasion you want to have mutable parameter.

wanton7 commented Feb 21, 2018

@alrz even (let x) => x +7 feels tedious if you have to write it everywhere. Maybe this could be split to two proposals? I think let as readonly var is a no brainer, but I think there should be project wide setting to default to readonly parameters. That would need new keyword like writable when there is rare occasion you want to have mutable parameter.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Feb 21, 2018

Contributor

I doubt it would ever happen because it results in a separate dialect of C#. Note that nullable opt-in warnings are just about warnings, not the syntax.

Contributor

alrz commented Feb 21, 2018

I doubt it would ever happen because it results in a separate dialect of C#. Note that nullable opt-in warnings are just about warnings, not the syntax.

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 21, 2018

@alrz you are probably right. Maybe you could still use writable keyword without that setting on, it just wouldn't do anything? I don't mind writing readonly for fields but using let or readonly everywhere for parameters just feels so wrong. I think most of new C# features are to make you write less code but that would make you write more, because readonly parameters are usually what you want.

wanton7 commented Feb 21, 2018

@alrz you are probably right. Maybe you could still use writable keyword without that setting on, it just wouldn't do anything? I don't mind writing readonly for fields but using let or readonly everywhere for parameters just feels so wrong. I think most of new C# features are to make you write less code but that would make you write more, because readonly parameters are usually what you want.

@knocte

This comment has been minimized.

Show comment
Hide comment
@knocte

knocte Feb 22, 2018

@alrz you are probably right. Maybe you could still use writable keyword without that setting on, it just wouldn't do anything? I don't mind writing readonly for fields but using let or readonly everywhere for parameters just feels so wrong. I think most of new C# features are to make you write less code but that would make you write more, because readonly parameters are usually what you want.

These are very good points. However, making this feature be introduced in the language without devs having to write more code would mean making parameters et-al be readonly by default, which would break previous code. I think it's worth it though, but maybe it should be introduced in a 2-phase basis: a warning that tells the dev to mark the parameter as writable (or rather mutable, I like this word more), if he's mutating it. And then in v.NEXT convert that warning to an error. So that all parameters are readonly by default from then on.

knocte commented Feb 22, 2018

@alrz you are probably right. Maybe you could still use writable keyword without that setting on, it just wouldn't do anything? I don't mind writing readonly for fields but using let or readonly everywhere for parameters just feels so wrong. I think most of new C# features are to make you write less code but that would make you write more, because readonly parameters are usually what you want.

These are very good points. However, making this feature be introduced in the language without devs having to write more code would mean making parameters et-al be readonly by default, which would break previous code. I think it's worth it though, but maybe it should be introduced in a 2-phase basis: a warning that tells the dev to mark the parameter as writable (or rather mutable, I like this word more), if he's mutating it. And then in v.NEXT convert that warning to an error. So that all parameters are readonly by default from then on.

@aluanhaddad

This comment has been minimized.

Show comment
Hide comment
@aluanhaddad

aluanhaddad Feb 22, 2018

@alrz

Note that any codebase that adopts readonly locals, would probably want to use it everywhere, imagine if the same codebase would like to use explicit types (even if not always) instead of var.. I don't think the result would look as clean as before. the worst part is that regardless of the codestyle of your choice, you'd need to use explicit types in patterns, as I mentioned above, it would give you is readonly Point p which is just weird.

I fully agree.

var q = from int item in list ...
var q = from item in list ...

those don't mean the same thing at all, so this is very scary.

The introduction of in parameters has already made things confusing.

public static void Foo(in object i)
{
    i = new object();
}

Cannot assign to variable 'in object' because it is a readonly variable
I seriously think the in syntax is extremely confusing. It also is as much about the caller as the callee, so it doesn't work well with this proposal at all since it is all about the callee.
And then there is this...

delegate TResult Func<in T, out TResult>(in T x);

Invalid variance: The type parameter 'T' must be invariantly valid on 'Func<T, TResult>.Invoke(in T)'. 'T' is contravariant.
Of course I know the two meanings of in are completely unrelated, but it is just a nasty wart on an otherwise clean syntax and one that could have been avoided. And it does make variance harder to explain.

I'm definitely starting to think this is not worth it anymore.

aluanhaddad commented Feb 22, 2018

@alrz

Note that any codebase that adopts readonly locals, would probably want to use it everywhere, imagine if the same codebase would like to use explicit types (even if not always) instead of var.. I don't think the result would look as clean as before. the worst part is that regardless of the codestyle of your choice, you'd need to use explicit types in patterns, as I mentioned above, it would give you is readonly Point p which is just weird.

I fully agree.

var q = from int item in list ...
var q = from item in list ...

those don't mean the same thing at all, so this is very scary.

The introduction of in parameters has already made things confusing.

public static void Foo(in object i)
{
    i = new object();
}

Cannot assign to variable 'in object' because it is a readonly variable
I seriously think the in syntax is extremely confusing. It also is as much about the caller as the callee, so it doesn't work well with this proposal at all since it is all about the callee.
And then there is this...

delegate TResult Func<in T, out TResult>(in T x);

Invalid variance: The type parameter 'T' must be invariantly valid on 'Func<T, TResult>.Invoke(in T)'. 'T' is contravariant.
Of course I know the two meanings of in are completely unrelated, but it is just a nasty wart on an otherwise clean syntax and one that could have been avoided. And it does make variance harder to explain.

I'm definitely starting to think this is not worth it anymore.

@gulshan

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan Feb 22, 2018

I vote for making parameters readonly by default with soft-break (warnings). I also think var can be reused to indicate mutability instead of verbose writable or mutable.

gulshan commented Feb 22, 2018

I vote for making parameters readonly by default with soft-break (warnings). I also think var can be reused to indicate mutability instead of verbose writable or mutable.

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Feb 22, 2018

@knocte That's not how Microsoft rolls. They will never force anyone to rewrite their program to upgrade their compiler to the new version. A whole lot of goodwill towards MS is due to them practically never breaking backwards compatibility.

@knocte That's not how Microsoft rolls. They will never force anyone to rewrite their program to upgrade their compiler to the new version. A whole lot of goodwill towards MS is due to them practically never breaking backwards compatibility.

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 22, 2018

@gulshan I think soft-breaking is the way to go for defaulting to readonly parameters.
But as for using var for making parameter mutable, I think it will look too much like tuple deconstruction when used in lambda and will make code harder to read. If something shorter is desired how about mut? Rust uses that as mutable keyword. I like writable because it's opposite of readonly

@orthoxerox Microsoft is already introducing soft-breaking for "Nullable Reference Types" . This could be done similar way. If that is opt-in then this could be as well.

wanton7 commented Feb 22, 2018

@gulshan I think soft-breaking is the way to go for defaulting to readonly parameters.
But as for using var for making parameter mutable, I think it will look too much like tuple deconstruction when used in lambda and will make code harder to read. If something shorter is desired how about mut? Rust uses that as mutable keyword. I like writable because it's opposite of readonly

@orthoxerox Microsoft is already introducing soft-breaking for "Nullable Reference Types" . This could be done similar way. If that is opt-in then this could be as well.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 22, 2018

Contributor

@wanton7

Just because the team is considering it in one circumstance doesn't mean the floodgates are open. If they did in this case as well that would make for 4 different dialects of the language. One more would make 8, and so on.

Non-nullable references solves a severe real-world problem. Readonly parameters solves nothing but a stylistic preference for the developer to gate themselves. It's also something very easy solved by an analyser if a developer wishes such rules to be enforced.

Contributor

HaloFour commented Feb 22, 2018

@wanton7

Just because the team is considering it in one circumstance doesn't mean the floodgates are open. If they did in this case as well that would make for 4 different dialects of the language. One more would make 8, and so on.

Non-nullable references solves a severe real-world problem. Readonly parameters solves nothing but a stylistic preference for the developer to gate themselves. It's also something very easy solved by an analyser if a developer wishes such rules to be enforced.

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 22, 2018

@HaloFour isn't that "Nullable Reference Types" mostly just an analyzer as well with some attributes and ? keyword sprinkled on top? I agree that it's lot more important feature than this. Important is probably too mild, it's amazing new upcoming feature.

Anyway I think readonly parameters should be separate proposal from let. Problems with readonly parameters shouldn't stop us adding let. JavaScript as an example has only readonly local variables with const.

Defaulting to readonly parameters (adding an analyzer) for everything except ref type parameters in, out and ref without any new keyword would work too. In those rare cases you actually need mutability you could just add mutable local variable and copy parameter there.

wanton7 commented Feb 22, 2018

@HaloFour isn't that "Nullable Reference Types" mostly just an analyzer as well with some attributes and ? keyword sprinkled on top? I agree that it's lot more important feature than this. Important is probably too mild, it's amazing new upcoming feature.

Anyway I think readonly parameters should be separate proposal from let. Problems with readonly parameters shouldn't stop us adding let. JavaScript as an example has only readonly local variables with const.

Defaulting to readonly parameters (adding an analyzer) for everything except ref type parameters in, out and ref without any new keyword would work too. In those rare cases you actually need mutability you could just add mutable local variable and copy parameter there.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 22, 2018

Contributor

@wanton7

isn't that "Nullable Reference Types" mostly just an analyzer as well with some attributes and ? keyword sprinkled on top?

Somewhat, although it affects locals to which attributes cannot be applied. It also involves a degree of flow analysis which is more involved than a typical analyzer.

Contributor

HaloFour commented Feb 22, 2018

@wanton7

isn't that "Nullable Reference Types" mostly just an analyzer as well with some attributes and ? keyword sprinkled on top?

Somewhat, although it affects locals to which attributes cannot be applied. It also involves a degree of flow analysis which is more involved than a typical analyzer.

@knocte

This comment has been minimized.

Show comment
Hide comment
@knocte

knocte Feb 22, 2018

If they did in this case as well that would make for 4 different dialects of the language.

Disagree with this. Look at the TypeScript approach: the controversial or, let's say, difficult to "make default" properties of the language have all been packed under a --strict flag. However, you can enable/disable each "strict" feature on its own (such as, strict null checks, which exists already there in a released version, not on previews). Having parameters not marked as mutable/mut make the compiler give errors could be another "strict" feature, that you could enable/disable at will.

knocte commented Feb 22, 2018

If they did in this case as well that would make for 4 different dialects of the language.

Disagree with this. Look at the TypeScript approach: the controversial or, let's say, difficult to "make default" properties of the language have all been packed under a --strict flag. However, you can enable/disable each "strict" feature on its own (such as, strict null checks, which exists already there in a released version, not on previews). Having parameters not marked as mutable/mut make the compiler give errors could be another "strict" feature, that you could enable/disable at will.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Feb 22, 2018

Contributor

It's also something very easy solved by an analyser if a developer wishes such rules to be enforced.

I'd prefer that. with source-only attributes you could annotate locals (in addition to parameters) to suppress mutation warnings. No new syntax required.

PS: that's only sufficient when there's no other feature with semantics that depend on locals being read-only.

Contributor

alrz commented Feb 22, 2018

It's also something very easy solved by an analyser if a developer wishes such rules to be enforced.

I'd prefer that. with source-only attributes you could annotate locals (in addition to parameters) to suppress mutation warnings. No new syntax required.

PS: that's only sufficient when there's no other feature with semantics that depend on locals being read-only.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 22, 2018

Contributor

@knocte

There is a Cartesian product of dialects for every possible combination of language flags that modify the behavior of the language like this. The language team has already stated that they are loathe to add new dialects. Non-nullable reference types is a unique unicorn in that sense, only considered because of the massive impact it would have. Readonly parameters will not have remotely close to that level of impact.

TypeScript is a very different beast given its design and intention. Even so you're just as unlikely to see them consider a compiler flag to enforce something so trivial as readonly parameters to functions.

Contributor

HaloFour commented Feb 22, 2018

@knocte

There is a Cartesian product of dialects for every possible combination of language flags that modify the behavior of the language like this. The language team has already stated that they are loathe to add new dialects. Non-nullable reference types is a unique unicorn in that sense, only considered because of the massive impact it would have. Readonly parameters will not have remotely close to that level of impact.

TypeScript is a very different beast given its design and intention. Even so you're just as unlikely to see them consider a compiler flag to enforce something so trivial as readonly parameters to functions.

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 23, 2018

I like to add something here from my experience using TypeScript and its const. In our company's codebase i've changed all let (it's basically C#'s var) to const (it's similar to let as readonly var proposal for C#) that I could in about 30 files. Every file is a component made with jQuery. Found two bugs, one was caused by bad merge and one was programmer error in code path that wasn't used often.

I know it isn't much but still it can prevent some programming errors or bad merges and having just let as readonly var is worth it without readonly parameters.

wanton7 commented Feb 23, 2018

I like to add something here from my experience using TypeScript and its const. In our company's codebase i've changed all let (it's basically C#'s var) to const (it's similar to let as readonly var proposal for C#) that I could in about 30 files. Every file is a component made with jQuery. Found two bugs, one was caused by bad merge and one was programmer error in code path that wasn't used often.

I know it isn't much but still it can prevent some programming errors or bad merges and having just let as readonly var is worth it without readonly parameters.

@gulshan

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan Feb 23, 2018

I admit, breaking current behavior to make all parameters immutable is too big of a change for too little. But if that is not doable, I think it's better to leave parameters as is. And focus should be on readonly locals using let, which would not be a breaking change.

gulshan commented Feb 23, 2018

I admit, breaking current behavior to make all parameters immutable is too big of a change for too little. But if that is not doable, I think it's better to leave parameters as is. And focus should be on readonly locals using let, which would not be a breaking change.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Feb 26, 2018

I don't understand why we'd want to make parameters immutable at the language level. As a code analysis rule, definitely. But for the compiler can someone explain the benefit?

I don't understand why we'd want to make parameters immutable at the language level. As a code analysis rule, definitely. But for the compiler can someone explain the benefit?

@wanton7

This comment has been minimized.

Show comment
Hide comment
@wanton7

wanton7 Feb 26, 2018

@Grauenwolf Only thing I can think of is that if you want to make some of them mutable. But if having all of them immutable is enough then code analysis will suffice.

wanton7 commented Feb 26, 2018

@Grauenwolf Only thing I can think of is that if you want to make some of them mutable. But if having all of them immutable is enough then code analysis will suffice.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 26, 2018

@Grauenwolf,

It's really just for consistency. If I can mark a field as read-only, why not a local variable? If I can mark a local as read-only, why not a parameter?

Whilst an analyzer can be used to enforce read-only as the default for parameters, the difficulty comes with marking a parameter as an exception. Attributes can't be applied directly to the parameters and have to be applied to the method, which is messy. So, currently, the code analysis approach isn't very nice, thus a compiler-based solution has appeal.

(turns out the last paragraph is utter tosh, so "deleted")

DavidArno commented Feb 26, 2018

@Grauenwolf,

It's really just for consistency. If I can mark a field as read-only, why not a local variable? If I can mark a local as read-only, why not a parameter?

Whilst an analyzer can be used to enforce read-only as the default for parameters, the difficulty comes with marking a parameter as an exception. Attributes can't be applied directly to the parameters and have to be applied to the method, which is messy. So, currently, the code analysis approach isn't very nice, thus a compiler-based solution has appeal.

(turns out the last paragraph is utter tosh, so "deleted")

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Feb 26, 2018

Attributes can be applied to parameters.

Heck, even the out keyword is really just an attribute when seen by other languages such as VB.

Attributes can be applied to parameters.

Heck, even the out keyword is really just an attribute when seen by other languages such as VB.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Feb 26, 2018

@Grauenwolf,

Ha, so they can. I was certain they couldn't be. Oh well, scrap what I previously said. I live and learn! 😆

@Grauenwolf,

Ha, so they can. I was certain they couldn't be. Oh well, scrap what I previously said. I live and learn! 😆

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Feb 26, 2018

@Grauenwolf

Attributes can be applied to parameters

While this is true, it's not a particularly satisfactory solution since attributes form part of the method signature. You don't really want to advertise to the outside world that you're going to mutate the variable you've used as input to the function: there's no way that it affects the caller to your method.

If we do get soure-only attributes though, as @alrz said above, this objection might go away.

@Grauenwolf

Attributes can be applied to parameters

While this is true, it's not a particularly satisfactory solution since attributes form part of the method signature. You don't really want to advertise to the outside world that you're going to mutate the variable you've used as input to the function: there's no way that it affects the caller to your method.

If we do get soure-only attributes though, as @alrz said above, this objection might go away.

@DavidArno DavidArno referenced this issue Feb 26, 2018

Closed

Proposal: single-assignment `let` variables #1341

1 of 4 tasks complete
@JesperTreetop

This comment has been minimized.

Show comment
Hide comment
@JesperTreetop

JesperTreetop Feb 26, 2018

I agree with @wanton7:

Anyway I think readonly parameters should be separate proposal from let. Problems with readonly parameters shouldn't stop us adding let. JavaScript as an example has only readonly local variables with const.

Being a C# developer who also codes in JavaScript/TypeScript and Swift, I miss the ability to express this intent. It does catch bugs and it provides useful, executable documentation for yourself, the future maintainer, the compiler and the IDE. If parameters are causing trouble, it's possible to ship a less controversial version without that part and still provide a lot of expressiveness. (You could even use immediate local let declarations on the parameter values if you wanted to.)

I agree with @wanton7:

Anyway I think readonly parameters should be separate proposal from let. Problems with readonly parameters shouldn't stop us adding let. JavaScript as an example has only readonly local variables with const.

Being a C# developer who also codes in JavaScript/TypeScript and Swift, I miss the ability to express this intent. It does catch bugs and it provides useful, executable documentation for yourself, the future maintainer, the compiler and the IDE. If parameters are causing trouble, it's possible to ship a less controversial version without that part and still provide a lot of expressiveness. (You could even use immediate local let declarations on the parameter values if you wanted to.)

@drewnoakes drewnoakes referenced this issue in gitextensions/gitextensions Mar 19, 2018

Merged

Introduce ArgumentBuilder #4662

@SHEePYTaGGeRNeP

This comment has been minimized.

Show comment
Hide comment

SHEePYTaGGeRNeP commented Mar 28, 2018

isn't the in modifier (for parameters, not locals)?
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-2

@JesperTreetop

This comment has been minimized.

Show comment
Hide comment
@JesperTreetop

JesperTreetop Mar 28, 2018

@SHEePYTaGGeRNeP: Nope. in is a way of passing a value type by reference but preventing changes to it unlike ref. The proposal here is to make this possible:

var x = 42;
x = 43; // reassigning a variable; allowed
let y = 42; // "let" may be something else
y = 43; // compiler error

@SHEePYTaGGeRNeP: Nope. in is a way of passing a value type by reference but preventing changes to it unlike ref. The proposal here is to make this possible:

var x = 42;
x = 43; // reassigning a variable; allowed
let y = 42; // "let" may be something else
y = 43; // compiler error
@SHEePYTaGGeRNeP

This comment has been minimized.

Show comment
Hide comment
@SHEePYTaGGeRNeP

SHEePYTaGGeRNeP Mar 28, 2018

@JesperTreetop in does not need to be a value type I believe, but yeah, in does not work for local variables.

@JesperTreetop in does not need to be a value type I believe, but yeah, in does not work for local variables.

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 28, 2018

@SHEePYTaGGeRNeP an in parameter doesn't need to be a value type, but it makes no sense to mark a reference type parameter as in.

@SHEePYTaGGeRNeP an in parameter doesn't need to be a value type, but it makes no sense to mark a reference type parameter as in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment