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

Champion "readonly for locals and parameters" #188

Open
gafter opened this issue Feb 26, 2017 · 340 comments
Open

Champion "readonly for locals and parameters" #188

gafter opened this issue Feb 26, 2017 · 340 comments

Comments

@gafter
Copy link
Member

@gafter 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
Copy link

@gulshan gulshan commented Mar 23, 2017

Any plan for readonly types(classes and structs)?

@Thaina
Copy link

@Thaina Thaina commented Mar 27, 2017

should support

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

@jnm2 jnm2 commented Mar 27, 2017

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

@Thaina
Copy link

@Thaina 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
Copy link
Contributor

@jnm2 jnm2 commented Mar 28, 2017

@Thaina Yes, I'm inclined to agree.

@HaloFour
Copy link
Contributor

@HaloFour 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
Copy link
Contributor

@jnm2 jnm2 commented Mar 28, 2017

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

@Thaina
Copy link

@Thaina 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
Copy link
Member

@benaadams benaadams commented Mar 28, 2017

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

@HaloFour
Copy link
Contributor

@HaloFour 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
Copy link
Member

@benaadams benaadams commented Mar 28, 2017

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

Fair enough 😄

@Richiban
Copy link

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

@soroshsabz
Copy link

@soroshsabz soroshsabz commented Mar 30, 2017

ITNOA

@Richiban where I can found it?

@Richiban
Copy link

@Richiban Richiban commented Mar 30, 2017

@soroshsabz

ITNOA

That's a new one!

dotnet/roslyn#7626 and https://github.com/dotnet/roslyn/issues/159 are probably what you're looking for.

@HaloFour
Copy link
Contributor

@HaloFour 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
Copy link

@soroshsabz soroshsabz commented Mar 30, 2017

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

@soroshsabz
Copy link

@soroshsabz soroshsabz commented Mar 30, 2017

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

@HaloFour
Copy link
Contributor

@HaloFour 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
Copy link

@soroshsabz 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
Copy link

@DavidArno DavidArno commented 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
);
@daniel-white
Copy link

@daniel-white daniel-white commented Jan 17, 2020

@Happypig375 that is an opt in thing just as the async modifier is.

@Happypig375
Copy link

@Happypig375 Happypig375 commented Jan 17, 2020

@daniel-white It is the default for all new projects.

@GeirGrusom
Copy link

@GeirGrusom GeirGrusom commented Jan 17, 2020

@jnm2

I work a lot with Kotlin and I almost never use var (i.e. mutable locals, I can't remember the last time I used this but maybe I have) because you don't actually want to overwrite locals 99.9% of the time but this doesn't actually stop you from making writable locals (just replace val with var). I think having readonly in method arguments is a complete and utter waste of time because it doesn't matter to the caller because C# is pass by value, and if you pass by reference we already have in that does that exact thing.

I don't think mutability of locals has any actual big impact or even value, but if they were to implement it I think the C# language developers has already settled what keyword they would use by introducing let in LINQ, and the value of readonly method parameters is basically none since it doesn't improve anything with regards to the caller.

@daniel-white
Copy link

@daniel-white daniel-white commented Jan 17, 2020

@Happypig375 exactly. new projects. theres no legacy code in there to break.

@JesperTreetop
Copy link
Contributor

@JesperTreetop JesperTreetop commented Jan 17, 2020

@jnm2 I would be interested in that analyzer. And those optimizations screw up, for example, Edit and Continue. Being able to differentiate between a write-once and reassignable variable would give a lot more freedom to do this optimization opportunistically even when it wasn't done before.

@Happypig375
Copy link

@Happypig375 Happypig375 commented Jan 17, 2020

@daniel-white It breaks whether a large portion of old code posted online emits warnings or not. In contrast, almost no one would call their types "let". Even then, this new feature could be shadowed by that type.

@jnm2
Copy link
Contributor

@jnm2 jnm2 commented Jan 17, 2020

@GeirGrusom I'm kinda not seeing the value of either readonly parameters or locals, especially after reading Neal's writeup (see). It seems like we are in violent agreement. 😊

@JesperTreetop Edit and Continue replaces the whole method IL body, doesn't it? Either way, debug builds don't normally have optimizations turned on.

@JesperTreetop
Copy link
Contributor

@JesperTreetop JesperTreetop commented Jan 17, 2020

@jnm2 I may have been wrong about this particular example - my point was that this optimization could become available more times in general because the code would carry more information about the intent (good for both the human and the machine).

@HaloFour
Copy link
Contributor

@HaloFour HaloFour commented Jan 17, 2020

@GeirGrusom

The problem with let (or val) is that it enshrines that if you want readonly locals you also need to infer the types of those locals. With languages like F#, Kotlin and Scala you can still opt-in to explicitly specifying the types of those variables. I'm of the opinion that C# still needs to support those explicit declarations with readonly-ness, and given that fields are declared in this exact same manner the syntax and behavior is already easy to understand.

@jnm2

I'm kinda not seeing the value of either readonly parameters or locals

Locals and parameters are by-value and safe from shared mutation, except when captured in a closure. In Java you solve for this by making locals or parameters final. As of Java 8 and the introduction of lambdas the language requires that the parameters or locals be "effectively final", in that they're only set once but not actually marked final, but that's something C# can't do without breaking existing code.

Even for non-captured variables and parameters it has long been considered a good practice to mark your variables and parameters final in Java. And while Java has var they punted on adding a final version of the shorthand because they considered that the requirement of being effectively final for captures was sufficient in nudging towards readonly variables.

In short, this hasn't been controversial in Java for nearly 25 years. It shouldn't be controversial in C#. 😁

@Grauenwolf
Copy link

@Grauenwolf Grauenwolf commented Jan 17, 2020

I don't see that as a problem. Most of the time it's going to be completely ok. When it's not, you have this...

let foo = (IFoo)GetBar();
@fr0
Copy link

@fr0 fr0 commented Jan 17, 2020

@GeirGrusom I don't think that disagrees with what I've said.

I used to think I wanted this feature, but Neal's writeup at #3107, section "Example of a language design pitfall," makes a lot of sense to me.

Also, it seems like methods that are complex enough to begin caring about tracking variables this way should probably be refactored into less complex methods.

I can't disagree with this enough. Immutable locals are useful for expressing my design intent, not just to the compiler but to future maintainers of the code - regardless of method length/complexity.

@HaloFour
Copy link
Contributor

@HaloFour HaloFour commented Jan 17, 2020

@Grauenwolf

I don't see that as a problem. Most of the time it's going to be completely ok.

For some developers, sure. But some developers really dislike inferred variables and some organizations forbid it. This is why Visual Studio tracks project preferences on the use of var and when it's ok (or not). It doesn't sound like the team is willing to add a readonly form of locals that excluded explicit variable declaration, and the types of parameters can't be inferred at all so the syntax needs to be solved anyway.

@Richiban
Copy link

@Richiban Richiban commented Jan 17, 2020

I know it might be too minor a benefit to consider implementing a language feature, but since I'm now doing a lot of Javascript/Typescript development I find it invaluable as a reader to be able to tell which locals are going to change vs which ones are going to stay the same.

Take this code:

function* iterLinesOutwards(
  document: vscode.TextDocument,
  currentLineNumber: number
) {
  let forwardsPointer = currentLineNumber + 1;
  let backwardsPointer = currentLineNumber - 1;

  while (forwardsPointerInBounds() && backwardsPointerInBounds()) {
    const backwardsLine = backwardsPointerInBounds()
      ? document.lineAt(backwardsPointer)
      : undefined;
    const forwardsLine = forwardsPointerInBounds()
      ? document.lineAt(forwardsPointer)
      : undefined;

    yield { backwardsLine, forwardsLine };

    forwardsPointer++;
    backwardsPointer--;
  }

  function forwardsPointerInBounds() {
    return forwardsPointer <= document.lineCount;
  }

  function backwardsPointerInBounds() {
    return backwardsPointer >= 0;
  }
}

The lets stand out like sore thumbs, letting you know, as the reader, which variables you have to watch out for in the method body as they will be mutated.

@JesperTreetop
Copy link
Contributor

@JesperTreetop JesperTreetop commented Jan 17, 2020

Is there anything to allowing let T xyz = ...; for explicitly typed read-only locals and only having let? I admit it looks a bit odd, but there's some precedence in the from clause which allows both from z and from T z.

@Grauenwolf
Copy link

@Grauenwolf Grauenwolf commented Jan 17, 2020

I want it for readability as well. If people are consistently using let, then var will stick out like a sore thumb. Which is exactly what I want to happen.

As for let T name =, my only concern is that it adds complexity to the implementation. If the language coders say, "no problem, it's fine" then I am ok with it as well.

@alrz
Copy link
Contributor

@alrz alrz commented Jan 17, 2020

I find it invaluable as a reader to be able to tell which locals are going to change vs which ones are going to stay the same

However, if the developer did not use const/let in the first place, you're out of luck.

In that case, I think the IDE is in a better position to help: dotnet/roslyn#37517 (comment)

@CyrusNajmabadi
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Jan 17, 2020

I see nothing wrong with let (Type?) id = ... being used for readonly locals. let id for the common case (and for users who prefer type inference like var). and let Type id for those that want readonly, but prefer to keep types explicit.

@JesperTreetop
Copy link
Contributor

@JesperTreetop JesperTreetop commented Jan 17, 2020

I find it invaluable as a reader to be able to tell which locals are going to change vs which ones are going to stay the same

However, if the developer did not use const/let in the first place, you're out of luck.

In that case, I think the IDE is in a better position to help: dotnet/roslyn#37517 (comment)

Being able to find out stuff about code when details are missing or not obvious is great and worthwhile, but it doesn't mean you shouldn't be able to express those things. If generics weren't around, I'd appreciate being able to have Visual Studio tell me which types of objects go into a Hashtable or ArrayList, but it doesn't mean I don't want generics.

@Thaina
Copy link

@Thaina Thaina commented Jan 18, 2020

If we need to specified type anyway. I think that we should fallback to use readonly Type variable instead of let Type variable and only use let when we don't need to specified a type. To make it align with member declaration

@Grauenwolf
Copy link

@Grauenwolf Grauenwolf commented Jan 18, 2020

Would let Type variable have a negative effect on code completion because it's always going to prompt a Type even if you are trying to enter a new variable name?

@JesperTreetop
Copy link
Contributor

@JesperTreetop JesperTreetop commented Jan 18, 2020

@Grauenwolf Oh dear - I hadn't thought of that. If so I withdraw my suggestion. Maybe that's a good reason to keep them separate as readonly T and let to begin with. Clearer for both the person and the machine and certainly for IntelliSense which has to bridge the gap. readonly T is longer but if you write the type it'll be longer anyway, and meanwhile the short option is always short.

@CyrusNajmabadi
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Jan 18, 2020

Intellisense can prompt for a type and allow you to type a name at the same time. We use this all over the place in C# already. For example, when you're typing a lambda, we have no idea of you're going to start with (Type1 p1, Type2 p2) => ... or do (p1, p2) => .... Can't have the parameter names completing out to type names. But we also still need to provide completion there.

@realvictorprm
Copy link

@realvictorprm realvictorprm commented Mar 25, 2020

well... let it be 😉 😄

@soroshsabz
Copy link

@soroshsabz soroshsabz commented May 28, 2020

Proposal added

Discussed in LDM

Decision in LDM

Finalized (done, rejected, inactive)

Spec'ed

See also dotnet/roslyn#115

I think this issue has Proposal in https://github.com/dotnet/csharplang/blob/master/proposals/readonly-locals.md , so I think @gafter can complete first check box and link to this proposal thanks to @stephentoub

@spixy
Copy link

@spixy spixy commented Jun 8, 2020

I dont see any reason to introduce new keywords (let, val...) when there already is readonly.
Same syntax as readonly members, nicely consistent:

private readonly SomeType type1 = new SomeType();

void C(SomeType value) {
    readonly SomeType type2 = value ?? type1;
}

If you would introduce let, would you then make instance members possible?

private let SomeType type1 = new SomeType();

Additional Considerations

I am not sure why it should or shouldn´t express object immutability. That is not responsibility of caller (method, function) but callee (type).

@chrisoverzero
Copy link

@chrisoverzero chrisoverzero commented Jun 8, 2020

I dont see any reason to introduce new keywords (let, val...) […]

let is already a C# keyword. And it already means a kind of un-reassignability:

Once initialized with a value, the range variable cannot be used to store another value.

@DavidArno
Copy link

@DavidArno DavidArno commented Jun 9, 2020

@spixy,

I dont see any reason to introduce new keywords (let, val...) when there already is readonly.

As @chrisoverzero says, let already is a keyword, which already implies "read-only". In addition, the proposal is (or was at one point) for these two to be semantically equivalent:

readonly SomeType type2 = value ?? type1;
let type3 = value ?? type1;

If we only had the first version, I'm fairly sure I'd never use this feature. Having to type readonly SomeType rather than var involves far too much noise just to get the compiler to enforce no reassignment of the value of a local variable.

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

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.