Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: let as the read-only counterpart of var #16182

Closed
alrz opened this issue Jan 2, 2017 · 37 comments
Closed

Proposal: let as the read-only counterpart of var #16182

alrz opened this issue Jan 2, 2017 · 37 comments

Comments

@alrz
Copy link
Member

alrz commented Jan 2, 2017

Disclaimer: This is particularly related to #16183 as the syntax has some overlaps with previously proposed pattern-matching statement (#6400). Also, I think if this is decided now, it might help if we prevent some syntactical forms to retain backward compatibility in the future (#13148).

Considering how common it will be to define read-only locals, readonly is too verbose to be used as a modifier on local variables (#115). Instead, I suggest we use let as the read-only counterpart of var:

var x = e;
let x = e;
ref var x = e;
ref let x = e;
var (x, y) = e;
let (x, y) = e;
(var x, var y) = e;
(let x, let y) = e;
M(out var (x, y));
M(out let (x, y));
foreach(ref var x in arr)
foreach(ref let x in arr)
e is var x
e is let x
case var x:
case let x:
(x, y) => {};
(let x, let y) => {};
((x, y)) => {};
(let (x, y)) => {};

Some statements like foreach and using define read-only variables by default. That could be the case for an analyzer to suggest to use let in those places to make that clear, visually.

However, parameters should use readonly just like fields, as they are more high-profile than locals.

readonly object field;
void M(readonly object parameter) {}

Note, unlike var, let is able to be used as a modifier on explicitly-typed locals,

let X x = e;

Notice that this is ambiguous with pattern-matching statement (#6400). See #16183 for more on that.

@aluanhaddad
Copy link

aluanhaddad commented Jan 2, 2017

On the whole, I support this proposal but, I have a few reservations.

However, parameters should use readonly just like fields, as they are more high-profile than locals.

I disagree. From the callers point of view, readonly should not even be visible. It is just noise. If the callee wants to annotate their parameters to avoid accidental re-assignment this should not be surfaced in the interface at all. The semantics are nothing like fields.

I suspect that the desire to use readonly for parameters, giving them a different notation and increasing the complexity of the mental model, is simply that

void Log(let string x) => Console.WriteLine(x);

just looks stupid.
I want to make a final plea for val over let.
Please see my comment in the original issue #115 (comment)
It works very well in both Scala and Kotlin, fits well into C#, and it reads much more naturally, especially in the context of existing language constructs.

Regardless, I want to reiterate that whatever the token is, it should not be surfaced to callers as it is the definition of an implementation detail.

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

From the callers point of view, readonly should not even be visible

I'm confused. This has nothing to do with callers, whatsoever.

I want to make a final plea for val over let.

We already have a let in query expressions.. I'm indifference towards either keyword, as long as it's not readonly. (edit: subsequent comments were making a good argument btw).

@aluanhaddad
Copy link

I'm confused. This has nothing to do with callers, whatsoever.

Indeed that was the point I was making. You say that parameters are more high profile and should use the readonly keyword. Since the readonlyness of parameters doesn't impact callers I don't see how parameters would be more high-profile.

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

In the past I was in favour of let mostly because the concerned that @HaloFour raised when it comes to scanning the code and because it's already used in the language but I'm leaning toward val because the points raised by @aluanhaddad.

@timopomer
Copy link

so basically c++ const ?
im afraid i dont see the need for this feature in c#.. especially considering let is a used keyword in linq expressions

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@timopomer Can you elaborate? why do you think we don't need this in C#? we already have readonly fields so why not parameters and locals? I fail to understand how LINQ has anything to do with this decision.

p.s. This will probably make the cut for the next version of C# this post is more about the syntax.

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@timopomer

especially considering let is a used keyword in linq expressions

That's why I prefer let for a readonly local shorthand. It mirrors the existing semantics of that keyword in LINQ queries, which is the declaration of an implicitly-typed readonly range variable.

My preference is still let over val not only for readability reasons but also because it's already a contextual keyword used in an almost identical manner. I don't see why there should be yet another keyword that does a nearly identical thing. In my opinion scanning through Scala code it is quite easy to miss val.

As for anything explicitly-typed such as parameters I think readonly is fine. Java uses the same modifier for readonly fields, parameters and locals and that works well enough.

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

As for anything explicitly-typed such as parameters I think readonly is fine.

Then if you want to make a variable read-only, it would depend on whether it is explicitly-typed or not, and you may need to change the modifier. Also readonly is too verbose to be present, possibly repeatedly, in type patterns, etc.

@timopomer
Copy link

timopomer commented Jan 2, 2017

@eyalsk
I want to know the case for let, in c++ its thread safety, compiler optimizations and compiler based documentation. but what is it here?

Also does it need its own keyword? wouldnt an implementation like Immutable<T> be enough? what with generic functions that take variables as references and change them? would it look like this?

void func<T>(T param1) where T : let

And what about unsafe code?

This would require more than just a compiler change, the JIT would need to be modified to make this work if im not wrong

and what if a variable is once passed as let and once normally and the normal variable is changed. do we get an exception?

Is it worth spending time on? i dont know. im just creating questions here that need answering

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@HaloFour

My preference is still let over val not only for readability reasons but also because it's already a contextual keyword used in an almost identical manner.

In try/catch block and switch statement they added when where they could reuse where, why? it does the same thing.

I don't see why there should be yet another keyword that does a nearly identical thing.

Because it looks better in all places and more consistent with var.

In my opinion scanning through Scala code it is quite easy to miss val.

IDE can improve the experience by providing a different color, I think that this would be sufficient.

As for anything explicitly-typed such as parameters I think readonly is fine. Java uses the same modifier for readonly fields, parameters and locals and that works well enough.

Why we need to be as verbose as Java? when we have opportunities to make it better?

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@eyalsk

Because it looks better in all places and more consistent with var.

We'll have to agree to disagree there. I don't think that val looks better than let, and I don't think the fact that val and var happen to share some letters makes them any more consistent with each other.

IDE can improve the experience by providing a different color, I think that this would be sufficient.

I have to scan code in git repos online frequently enough for me to have a strong opinion against that languages that rely on IDE syntax highlighting/coloring to be legible.

Why we need to be as verbose as Java? when we have opportunities to make it better?

Same number of tokens. A couple of extra characters. The argument of verbosity is so thin there that I don't think that it applies. Are you also suggesting that val be used for readonly fields?

@timopomer

The readonly parameter/local proposal doesn't intend to take things as far as C++ const. It would only prevent reassignment of the variable. It would not prevent the callee from mutating a reference:

void Foo(readonly MyClass obj) {
    obj.Value = "12345"; // legal
    obj = new MyClass(); // not legal
}

The feature exists specifically to allow a function author to prevent accidentally overwriting variables. It doesn't have any effect on callers.

@timopomer
Copy link

@HaloFour

In that case all this should be is a sign that you cannot assign anything to the let? as if the "=" operator would be overloaded and gave a compiler error when attempted to be used?

I guess thats ok, but i dont think it would give us any form of optimization

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

i dont think it would give us any form of optimization

@timopomer It does not have anything to do with optimizations, specifically that Roslyn generally do not do optimizations and it is usually deferred until runtime.

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@timopomer

I want to know the case for let, in c++ its thread safety, compiler optimizations and compiler based documentation. but what is it here?

You're wrong the C++11 specification does not say anything about const being thread-safe, however, the STL specification does.

So if you're consuming something from the STL you can have some guarantees about thread-safety given that your own objects satisfy the requirements.

You can watch the video by Herb Sutter to verify this

In C# readonly means that you simply put cannot reassign a value to a readonly variable unless you do that from the constructor.

Also does it need its own keyword? wouldnt an implementation like Immutable be enough? what with generic functions that take variables as references and change them? would it look like this?

I don't know what Immutable<T> would mean because this could only guarantee that the wrapped value isn't mutated but the variable that holds the instance of Immutable<T>itself can still be mutated so this would be useless.

And what about unsafe code?

Don't know, don't care, this is something that the design team need to decide on.

This would require more than just a compiler change, the JIT would need to be modified to make this work if im not wrong

Again, we're not speaking about implementation here so whatever it takes.

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@HaloFour

We'll have to agree to disagree there. I don't think that val looks better than let, and I don't think the fact that val and var happen to share some letters makes them any more consistent with each other.

The consistency doesn't lie in their shared letters but in the fact that they complement one another and by that I mean one represents a variable that can change and the other represents a value that cannot change.

Now, I agree that this isn't a reason to tipped the scales, I'm just stating my opinion here.

I have to scan code in git repos online frequently enough for me to have the strong opinion against that languages that rely on IDE syntax highlighting/coloring to be legible.

Fair point, in fact, I think I wrote this exact reason back then but dunno I changed my mind and the more I think about it I just like val better.

Same number of tokens. A couple of extra characters. The argument of verbosity is so thin there that I don't think that it applies. Are you also suggesting that val be used for readonly fields?

No, I don't suggest that val would be used for fields and I agree that the argument of verbosity doesn't hold much water but I dunno I just think that if we can make it terser and still clear then why not?

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@eyalsk

I don't know that the argument still stands but it had been mentioned in comments earlier that var makes no implications regarding the mutability of the variable. Whether let or val it would just be shorthand for readonly var which would be shorthand for readonly <type>.

Given the IDE argument, you'd be typing the same amount of characters to add either val or readonly modifier. It's literally f<tab> in IntelliJ for Java today.

Either way, that's also just my opinion, and it's not that strong of an opinion. Certainly not my hill to die on. I think it's wise to start with readonly as a general modifier and then work on whatever shorthand would be reasonable.

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

@HaloFour Why do you think we should start with something that requires a shorthand in the first place?

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@alrz Same reason preoptimization is bad.

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@HaloFour

Either way, that's also just my opinion, and it's not that strong of an opinion. Certainly not my hill to die on. I think it's wise to start with readonly as a general modifier and then work on whatever shorthand would be reasonable.

You convinced me with this! Sorry I'm torn! 😆

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

@HaloFour

It pushes more complexity to the syntax and the compiler rather than simplifying things. It doesn't really make sense to me. It's mentioned before that anonymous methods were a mistake. But that was when the actual use cases were not identified. This is just like that except now we know it will be very common to use read-only locals.

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@alrz

Readability is significantly more important than either syntax or compiler complexity. Humans have to read and parse that code too. I'm also of the opinion that readonly locals will end up being relatively rare. A tool to make functional developers feel more at home at best. Java only required it to make closures simpler but as of 8 they made the keyword optional.

So you intend for this proposal to supplant #115 rather than augment it?

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@HaloFour

The reason I'm so torn on this is because I think that what you propose make sense for the short-term but not the long-term if in the long-term it's going to be viral enough to justify a syntactic sugar and we're going to choose either let or val then it would be a matter of style and I don't think we need to introduce such a choice into the language.

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

@HaloFour

I'm also of the opinion that readonly locals will end up being relatively rare.

I disagree. you should always use read-only locals unless they are not read-only. Actually it will be a good candidate for an analyzer to suggest to make locals read-only if they are not assigned to, exactly because humans have to read and parse that code. It's a good practise in JS too that you use let or const for variables unless you need mutability.

So you intend for this proposal to supplant #115 rather than augment it?

I made this proposal considering the overlap it entails with pattern-matching statement (#6400). See the disclaimer at the beginning of the OP.

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@eyalsk

What does "short-term" or "long-term" have to do with anything? var doesn't completely replace local declarations. There are times where you want to be explicit, either because you want the variable to be a different type or because you simply dislike implicit typing (there have been several proposals to this repo to allow for banning the use of var, and that's supported as a style.) I don't think that the more explicit form is irrelevant, and I don't think it's necessary to have completely different explicit forms for fields vs. locals vs. parameters.

@alrz

I disagree. you should always use read-only locals unless they are not read-only.

Indeed, a mindset of functional programming. I even agree with it, for the most part. But I don't think that's particularly relevant. When it comes to adoption existing C# programmers aren't likely to reach for something new that doesn't buy them some obvious benefits that makes their immediate lives easier. This won't reduce the amount of code that anyone needs to type and the argument that it makes code more maintainable is an abstract one.

I made this proposal considering the overlap it entails with pattern-matching statement

The #6400 proposal is dead in the water. It relied on pattern variables being immutable which is no longer the case. Use of it with readonly locals was also only a form of shorthand for let var x = y;. I expect at this point that #115 would be closer to consideration than something that depends on the now-defunct pattern matching behavior. I'm not at all opposed to a form of readonly pattern variable, though, to complement var.

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

@HaloFour

The #6400 proposal is dead in the water.

Unless it is just moved to pattern-matching spec draft?

It relied on pattern variables being immutable which is no longer the case.

The form let x as the shorthand for let var x relies on pattern variable being immutable. But the use cases remain intact. Actually with #16183 and #16182 you can have the best of both worlds.

I expect at this point that #115 would be closer to consideration than something that depends on the now-defunct pattern matching behavior.

The two proposal are irrelevant to each other (pattern-matching and readonly locals). But they can be used together e.g. case Point { X: let x, Y: 0 } = e else return.

Indeed, a mindset of functional programming.

How is that a mindset of functional programming and from when it is a taboo to adopt functional programming's good practice?

something new that doesn't buy them some obvious benefits that makes their immediate lives easier.

That was your own argument, because humans have to read that code? You claim verbosity is irrelevant to the readability but also using a read-only local in the first place is irrelevant too?

This won't reduce the amount of code that anyone needs to type

It does if you are willing to use read-only locals. At this point I'm positive your password is leaked.

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@alrz

That proposal died when the scoping rules and mutability of pattern variables changed. The spec hasn't been changed to reflect that nuclear bomb but I expect what is proposed for C# 8.0 will barely resemble it.

I make the argument that any and all code should be easily readable, based on it's grammar. That has nothing to do with my assertion that readonly locals will find limited adoption amongst the C# audience. This isn't because readonly locals are bad or that its somehow wrong to follow functional practices. I just don't think most C# programmers will care and I don't think you'll be able to convince . I think that the changes to the pattern matching spec regarding the mutability of pattern variables reflects this reality.

@iam3yal
Copy link

iam3yal commented Jan 2, 2017

@HaloFour

What does "short-term" or "long-term" have to do with anything?

What I meant by short-term and long-term is if after some time readonly var is going to be viral and then a syntactic sugar might get introduced in the form of val or let it would be just a matter of style and nothing more.

var doesn't completely replace local declarations. There are times where you want to be explicit, either because you want the variable to be a different type or because you simply dislike implicit typing

I wasn't speaking about readonly <type> but readonly var! I think that an explicit choice should be a decision that the developer makes and not something that is decided by the language.

So to make it crystal clear what I meant is as follow:

readonly int x = 1; // C# X
val x = 1; // C# X

Introducing val early as opposed to having it in a future version.

readonly int x = 1; // C# X
readonly var x = 1; // C# X
val x = 1; // C# X+1: syntactic sugar to readonly var

@gafter
Copy link
Member

gafter commented Jan 2, 2017

We are not contemplating any additional feature work for C# 7.

@alrz
Copy link
Member Author

alrz commented Jan 2, 2017

@gafter This is not a proposal for C# 7, although I think there will be a compat-issue similar to #13148. If read-only locals are planned for a point release, (per #16155 (comment)) wouldn't it make sense to settle that at this point?

@gafter
Copy link
Member

gafter commented Jan 2, 2017

@alrz There are currently no such plans (for read-only locals or readonly refs) for a point release. We have a longish list of things we might or might not do in the future. readonly refs has been tentatively categorized by the @dotnet/ldm to be considered for possible inclusion in the timeframe of C# 8 (i.e. not a point release). I suspect @jaredpar was hinting that he has other ideas for prioritizing that work that may change when we consider them.

That feature hasn't been designed yet; it is an open question whether the relevant keyword would be readonly, let, val, or some combination of them. Absent that design work, it would be premature to make changes in C# 7 responsive to that. It is also very very late in the C# 7 schedule, so making any changes is risky, if even possible.

I do not agree with comments on that thread that a ref foreach variable would necessarily need to be a ref readonly. All ref variables are readonly today in the sense that the ref cannot be reassigned to refer to something other than what it initially referred to. However, ref variables can (today) always be used to reassign the thing they refer to, and I think that would be a reasonable design point for a foreach ref feature, if we ever did such a thing.

@HaloFour
Copy link

HaloFour commented Jan 2, 2017

@eyalsk

I agree. I'd like to see let (or val) implemented alongside and at the same time as any general readonly modifier. It would mean the same thing as readonly var.

@jaredpar
Copy link
Member

jaredpar commented Jan 3, 2017

If read-only locals are planned for a point release, (per #16155 (comment)) wouldn't it make sense to settle that at this point?

In that comment I was referring to readonly ref being a candidate for a point release off of C# 7. It's definitely not firmly planned yet, nor did it include readonly locals in general. That's a different feature than readonly ref.

@DavidArno
Copy link

DavidArno commented Jan 3, 2017

@gafter,

We have a longish list of things we might or might not do in the future. readonly refs has been tentatively categorized by the @dotnet/ldm to be considered for possible inclusion in the timeframe of C# 8 (i.e. not a point release).

Could this list be published please and maintained in the open, so we all have up to date info on the team's thinking on this matter?

@gafter
Copy link
Member

gafter commented Jan 3, 2017

@DavidArno I'll ask around about that.

@HaloFour
Copy link

HaloFour commented Jan 3, 2017

@gafter

How much does that list align with the proposals on this repo? I don't see one specifically for "readonly ref". There are already more than 50 issues with the "Ready" tag and well over 400 others queued up behind that. It'd be nice to know that there is some motion behind those proposals rather than them being semi-permanently stuck in limbo.

@orthoxerox
Copy link
Contributor

Well, this list might be a good start: lots of pattern matching, records/ADTs, but nothing about immutability or non-nullability. I wish to know if they are still considered for vNext as well.

@alrz
Copy link
Member Author

alrz commented Jan 4, 2017

@orthoxerox I think this one is more reliable, however, it doesn't look comprehensive or longish.

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

No branches or pull requests

9 participants