TypeScript
TypeScript copied to clipboard
[feature] class properties that are "readonly in public, writable in private" or other permutations
Search Terms
typescript readonly on the outside writable on the inside
- https://stackoverflow.com/questions/46634876/how-can-i-change-a-readonly-property-in-typescript
Suggestion
Some sort of syntax to describe readonly on the outside, writeable on the inside
for a given property, so we can avoid making a getter just for this purpose (or using any of the convoluted options linked in that StackOverflow post).
Use Cases
To make this pattern easier to express.
Examples
instead of having to write
class Foo {
private _foo = 123
get foo() {
return this._foo
}
changeIt() {
this._foo = 456
}
}
const f = new Foo
f.foo = 345 // ERROR
we would be able to write something shorter like
class Foo {
publicread foo = 123
changeIt() {
this.foo = 456
}
}
const f = new Foo
f.foo = 345 // ERROR
Checklist
My suggestion meets these guidelines:
- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- [x] This feature would agree with the rest of TypeScript's Design Goals.
Previously at #2845 but we should revisit after so long
Just a note, but #2845 is similar but different. This one is not about making a getter/setter, but about making only a property, and being able to specify that the property be readonly on the outside of the class, and writable only inside the class (and subclasses too, depending on the keyword used)
Taking the idea a little further:
class Foo {
// readonly in public code or subclasses, writable in this class only
publicread privatewrite foo = 123
// readonly in public code, writable in this class or subclasses
publicread protectedwrite bar = 123
// not visible in public code, readonly in subclasses, writable in this class only
protectedread privatewrite baz = 123
}
and otherwise public
is equivalent to publicread publicwrite
, protected
is equivalent to protectedread protectedwrite
, and private
is equivalent to privateread privatewrite
, where the write
specifiers can not be less strict than the read
specifiers (or can they?).
I'd love this feature in Typescript. It would allow me to simplify so many of my classes, especially in libraries and packages that expose a public API.
I'm not a huge fan of the publicread privatewrite
syntax proposed by @trusktr as those keywords feel a little 'forced' to me. How about using the existing readonly
keyword and allowing multiple access modifiers in increasing order of specificity (public > protected > private). Access is then resolved from right to left, with the first matching access modifier defining the access level and readonly status:
// Seen as `private foo` by class members
// Seen as `public readonly foo` by other code
public readonly private foo : Foo;
// Seen as `private foo` by class members
// Seen as `protected readonly foo` by subclass members
// Not accessible by other code
protected readonly private foo : Foo;
// Seen as `protected foo` by class and subclass members
// Seen as `public readonly foo` by other code
public readonly protected foo : Foo;
// Would technically be valid as well, though not very useful
// Seen as `private readonly foo` by class members
// Seen as `protected foo` by subclass members
// Seen as `public readonly foo` by other code
public readonly protected private readonly foo : Foo;
// Throws an error on compile, since the `private` modifier is more specific than `public`
private public readonly foo;
The upside of this would be that we don't introduce new keywords, and the syntax is flexible enough in case other modifiers like readonly
are added.
A downside would be that function signatures like these are a little harder to parse visually when reading the code. But then the same could be said for everything that adds more keywords.
A question here would be how to handle the 'no access modifier = public' rule that Typescript uses. Should the public
modifier be required for signatures with multiple access modifiers, or would the following also be valid?
readonly private foo : Foo;
I'd argue that this could get confusing (was this intentional or did someone mix up the order of keywords?) so I think an explicit public
modifier should be required.
Note: this is written by someone who has no knowledge of the Typescript compiler or language design in general. So I don't know how easy/difficult/impossible this would be to implement. I'm curious to get some feedback on the syntax and on why this is or isn't a good idea.
Just encountered a use case for this today when writing out a value ref class.
class Ref <T> {
readonly value: T;
constructor (value: T) {
this.value = value;
}
set (value: T) {
this.value = value; // errors unless I override the type of `this`
return value;
}
// ...
}
Overriding the type of this
, can get annoying fast if you have lots of methods that need write access.
There are other ways around it like storing the value in a private field then creating a getter, but that adds a performance penalty.
@trusktr It might be best to default reads to always be public.
// Seen as `private foo` by class members
// Seen as `public readonly foo` by other code
privatewrite foo: string
This goes along with how typescript works now, fields are by default public and you narrow their scope using private or protected. My guess is it would also be easier to accommodate most cases without the need to explicitly define both read and write.
Also here's how I am getting around this issue right now. It's a bit cumbersome, but maybe it will help out others who desire this feature.
type Writeable <T> = {
-readonly [P in keyof T]: T[P];
};
type WritebaleRef <T> = Writeable<Ref<T>>;
class Ref <T> {
readonly value: T;
constructor (value: T) {
this.value = value;
}
set (this: WritebaleRef<T>, value: T) {
this.value = value; // no error because `this` type is changed to WritebaleRef<T>
return value;
}
// ...
}
This is one of the features I miss so much in TypeScript.
I don't really like the publicread privatewrite
notation. Is there an other language that uses that notation?
Here are a few suggestions:
// Option 1: C# style
public name: string { get; private set; }
// Option 2: Swift style
private(set) name: string
// Option 3: Swift struct-style
public readonly name: string
mutating changeName(name: string) {
this.name = name
}
// Option 4: New keyword
public frozen name1: string
public readonly name2: string
My choice would be the Swift style (option 1) or C# style (option 2). Option 3 is for if we can't change the property definition.
I also like the C# syntax but I think it's too far from the way getters and setters are defined in JS/TS so it would look out of place. Swift syntax looks nice though.
Neat to know how other languages do this.
@woubuc Your idea is great if we need to prevent from adding new keywords.
With the new keywords I suggested we can do things like
publicwrite protectedread privatewrite foo = 123
which makes foo
writable only in public and private code, but not in subclasses (it could be a valid use case, f.e. "I want this class to handle user input for foo, but I don't want subclasses to interfere with user input of foo, while the subclasses can still extend functionality in other areas").
@woubuc How would we specify the same thing with the space-separated existing keywords? Would it be
public protected readonly private foo = 123
?
Also with the new keywords, order wouldn't matter (or at least, the intention is clear regardless of order):
privatewrite protectedread publicwrite foo = 123
Here's my attempt with the existing keywords:
private protected readonly public foo = 123
But with the new keywords, what would happen if someone tries to use the existing keywords?:
privatewrite protectedread protected publicwrite foo = 123
Perhaps they'd need to be mutually exclusive, so we can either use only the existing keywords, or only the new ones, but not both.
The Readonly<T>
generic type (or an interface) will do the "public readonly, private writable" pattern.
interface IFoo {
readonly foo: number
}
class Foo implements IFoo {
foo = 123
changeIt() {
this.foo = 456
}
}
const f: Readonly<Foo> = new Foo
const g: IFoo = new Foo
@whzx5byb I appreciate your example, but it is not a solution to the problem.
- the compiler would still allow me to create an instance of
Foo
and then set thefoo
variable - this forces the developer to use interfaces instead of classes
- this becomes impractical in more complex constructs like inheritance
I'm not sure overly precise granularity is useful if it makes us write crazy things to define properties?
I think something simple like the following would do the trick:
class Foo {
/**
* foo can be read from public, protected and private contexts,
* writable only within the class and its subclasses.
*/
public protected foo: number;
}
class Bar {
/**
* bar can be read from public, protected and private contexts,
* writable only within the class.
*/
public private bar: number;
}
class Buzz {
/**
* buzz can be read from protected and private contexts,
* writable only within the class.
*/
protected private buzz: number;
}
Adding "larger" modifiers would only expand readability, and writabiIity would be kept to the most restricted modifier. ~~We can finally add the readonly
keyword in this word soup to close this last scope and never be able able to write.~~
edit: Actually you would never need to mix the readonly
keyword with more than one access modifier.
I don't really see a case where someone would want it the other way around, like "public read/write and protected readonly".
"public read/write and protected readonly".
@marechal-p The example I showed above was "public read/write, protected readonly, private read/write". In that concept, this allows the public (end users) to pass data in, and only allows the private scope to have full control (subclasses aren't afforded this).
With your idea (which I do like as a subset and feel that it would already be a lot better than what we currently have), how could we implement similar? Seems impossible.
I think being able to distinguish public input from protected input (in the private scope) could be useful. But I would totally settle with your idea over the current.
In that concept, this allows the public (end users) to pass data in, and only allows the private scope to have full control (subclasses aren't afforded this).
My opinion here is that subclasses should be seen as "end users" hence why I found that this was odd. In your example the field should simply be public
(r/w in public/protected/private contexts) because if anyone can change the value from outside then the class private implementations cannot make assumptions about it: it will be mutated by random clients, in public or protected scopes, doesn't matter.
My proposal fits the issue's title "readonly on the outside, writable on the inside" and allows one to avoid the private variable with public getter pattern with minimal syntax modification:
// useless boilerplate, runtime implications:
private _a
public get a() {
return this._a
}
// less runtime shenanigans
public private a
It is my belief that doing like your example proposes and having "public r/w, protected read-only, private r/w" is unsound. I have a hard time imagining an API design that would need such specificity, and I would assume the design itself needs to be changed.
It is my belief that doing like your example proposes and having "public r/w, protected read-only, private r/w" is unsound. I have a hard time imagining an API design that would need such specificity, and I would assume the design itself needs to be changed.
Protected read-only and yet public read/write is an oxymoron. I have to imagine public accessibility must be a strict subset of protected accessibility, which in turn must be a strict subset of private accessibility. Anything else is, on a conceptual level, unenforceable (e.g. you can easily and unintentionally circumvent such an access restriction via helper function).
As far as syntax, I'm not a huge fan of public private myProperty
. I think the word readonly
should be in there since that's what we're dealing with. I'd vastly prefer what @woubuc suggested. This reads more naturally and clearly.
// Publicly read-only, otherwise protected.
public readonly protected foo: Bar;
// Protected read-only, otherwise private.
protected readonly private foo: Bar;
:+1: for this, and I like the swift style as the most readable (it's also not too verbose).
Putting multiple of the same modifier in a row, as public private
or public readonly private
is a recipe for disaster imo.
My problems with this are:
- There's no way people would understand what's written unless they read the specific section in the docs that discribes what multiple public/private modifiers mean.
- It's different to convention in other languages.
- This syntax would be really hard to google or ask questions for because search results for public, protected and readonly modifiers standalone would be returned as priority.
Reasons I like the swift style:
- As someone mentioned the C# style is different enough from the way getters and setters are specified to make it confusing for new coders, particularly those that have used C# before (do I use
get()
or{get()}
to make a getter, I've seen both in use?). -
private(set)
very obviously does something different toprivate
so it's easier to pick up on possible access level problems at a glance.public readonly private
can easily be mistaken forpublic readonly
which does something quite different. -
private(set)
Gives a pretty good indication of what it's doing if you've never seen the syntax before, and you can be super explicit withpublic(get) private(set)
if you're working with people that don't commonly use typescript. -
private(set)
is less verbose thanpublic private
orpublic readonly private
(not really a big deal for me, but some people care about this).
The downsides to using the swift style is that private(get) property
would be an alias for private readonly property
(not a big problem, but slightly confusing for new coders).
Putting multiple of the same modifier in a row, as
public private
orpublic readonly private
is a recipe for disaster imo. My problems with this are:* There's no way people would understand what's written unless they read the specific section in the docs that discribes what multiple public/private modifiers mean.
public private
seems confusing for obvious reasons. public readonly private
might give you a moment of "heywhat" the first time, but I only see one way you can reasonably interpret it. If you guess, you may not feel confident in your guess, but it will almost certainly be correct. As far as googling it, anyone trying to program should have the know-how to use quotes with Google. (Guess what the very first result for "public readonly private"
is.)
Nonetheless, it may not be ideal. Just my personal preference. There are plenty of other options. public get private set
seems pretty straightforward to me. I do grimace at suggestions involving punctuation. Javascript already tends to get dense with parens and braces. The further you move from having them depict the structure of your logic, the harder everything is to parse. Not to mention Google tends to ignore punctuation in searches, even inside quotes.
This is one of the features I miss so much in TypeScript.
I don't really like the
publicread privatewrite
notation. Is there an other language that uses that notation?Here are a few suggestions:
// Option 1: C# style public name: string { get; private set; } // Option 2: Swift style private(set) name: string // Option 3: Swift struct-style public readonly name: string mutating changeName(name: string) { this.name = name } // Option 4: New keyword public frozen name1: string public readonly name2: string
My choice would be the Swift style (option 1) or C# style (option 2). Option 3 is for if we can't change the property definition.
I think the 2nd one is really good and wouldn't break existing code and it basically don't add new keywords.
This issue should really be worked on.
If you perform a cast, then the value can be changed (playground), although it looks a little strange.
...public accessibility must be a strict subset of protected accessibility, which in turn must be a strict subset of private accessibility
This shaped the following model in my head
If you add to the cited prospsition that allowing write implies allowing read you can make a list like this. Here every "capability" below "code" is alowed and everything above is forbidden
capability | code |
---|---|
public | |
public write | |
public(get) | |
public read | |
protected | |
protected write | |
protected(get) | |
protected read | |
private | |
private write | |
private(get) | |
private read |
@mixtur So would your proposition look like the following?
class Foo {
public(get) protected name: string;
}
@marechal-p
@mixtur So would your proposition look like the following?
class Foo { public(get) protected name: string; }
No. Just
class Foo {
public(get) name: string;
}
This would mean that the only forbidden thing is public write.
Ow. I see your point. So one actually might want to forbid writing from both protected and public, but allow reading for everyone.
@mixtur my point was just to see how it plays out... To expand more: with your notation, if we want something to be protected but allow reading from public we would just write public(get)
and nothing more? Felt weird to omit the protected
keyword.
More than two keywords feels noisy.
I though about it and I was actually wrong with my model. It doesn't work like a "list". I drew this chart. Cases 4 and 5 are falling out of the list representation.
So according to this I suggest
case 1 - public(get)
case 2 - protected(get)
case 3 - private(get)
cases 4 and 5 I think are rare so it is fine for them to require more noisy syntax.
case 4 - public(get) private(set)
case 5 - protected(get) private(set)
cases 4 and 5 I think are rare so it is fine for them to require more noisy syntax
@mixtur case 4 is what inspired this proposal, so I disagree that it would be rare I think it would be the most used of any of the new cases, followed by case 1, then 2.
Case 3, and 5 already have working syntax:
case 3 - private readonly
case 5 - protected readonly
My syntax suggestion for the additional cases would be:
case 1 - protected read(public)
case 2 - private read(protected)
case 4 - private read(public)
Where read([public|protected])
must follow [protected|private]
similar to readonly
but instead of removing write privilege it elevates read.
Case 1 and 4 could be further simplified if read
implied read(public)
:
case 1 - protected read
case 4 - private read
Like @robbiespeed mentioned, cases 3 and 5 are already supported.
Then I'd go with either A:
case 1. public readonly protected
case 2. protected readonly private
case 4. public readonly private
Or either B:
case 1. public protected
case 2. protected private
case 4. public private
I'm quite a fan of using the get and set words instead of read and write because in my mind it maps better to getters and setters having different permission levels than the readonly keyword. It will also gel well with the (presumably also provided capability) of having getters and setters with different access levels, so public private(get) would be the same as:
private get () {...}
public set () {...}
Again @marechal-p don't use "protected readonly private" because it's not at all intuitive. Cases can be made for why read is protected (because it's preceeded by the protected modifier which is how the rest of typescript works) or how read is public (because public is a modifier on the readonly keyword). The answer "well if they look it up in the docs they'll know" is not good enough when we have proposals that are not confusing.
Also I agree with @mixtur about the syntax for case 4 and 5, not because it's rare but because it's very easy to understand. I'm currently in the process of onboarding several juniors and I'd rather not have the "I wanna type less characters" argument cause a ton of extra work for me.
@Griffork currently in TypeScript readonly
must follow the access modifier. So protected readonly private
following current TypeScript conventions would mean that readonly
applies to protected
, with the added r/w access from private
contexts. I don't think it's any more confusing than seeing things like:
class Foo {
constructor(readonly bar: string, baz: readonly string[]) {}
}
Some things are learned once and never forgotten. I only keep proposing this syntax because it doesn't add new keywords, just a different way to interpret it. I also think that ordering of the keywords should be important, with the most public access modifier on the left, and the most private on the right. So syntax like protected read(public)
feels counter intuitive to me, but that's personal.
Building on @mixtur proposed syntax, I could see something like:
case 1. public(get) protected
(or just public(get)
?).
In this case with public(get)
only, one issue for first time users would be to wonder how it is different from public readonly
which doesn't grant you write access from other scopes. Either something to be learned from docs or use public(get) protected
.
case 2. protected(get) private
or just protected(get)
.
case 3. private readonly
already supported by TypeScript.
case 4. public(get) private
This last case should be the reason why using public(get)
alone would be ambiguous, so it seems better to always use public(get) (protected|private)
here.
case 5. protected readonly
already supported by TypeScript.
My only concern with this syntax is how it doesn't feel consistent with what is already supported in TypeScript (cases 3 and 5). My proposed syntax at least stays somewhat consistent.
@robbiespeed syntax seems unambiguous, but I'm just not a fan of the new read
keyword. But if we re-use readonly
we end up with things like protected readonly(public)
and at this point I wouldn't find public readonly protected
much more confusing.
But in the end no matter what the syntax ends up being I won't be too picky anyway, as this is a feature I had a use for.
@marechal-p I'm going to continue bikeshedding on this issue just for a bit.
It's may be obvious to you but that doesn't mean it's going to be obvious to other people. Just because you have a reason in your head as to why it makes sense doesn't mean everyone else is going to come to the same conclusion. For example it's not obvious to me and I've been using Typescript since 0.8 and consider myself to be very proficient with it. I don't think I'm particularly stupid or abnormal here, I think I'm just coming from a different place with different expectations and a different thought process, which is why I'm so adamant that this is a bad idea. And if I can come to the wrong conclusion, won't other people also have the same problem?
Yes I understand it after I've had it explained to me. Yes I think I'd get confused multiple times while using/reading it before it actually sticks. Yes I think people with no prior Typescript experience coming from other languages (C#, python or Javascript etc) would be confused reading this syntax.
I am already spending a lot of time teaching people typescript syntax because it's so different to other languages, I'm not in favour of using "existing keywords" when a much clearer alternative is available.
On another note, we need a new keyword for setting anyway don't we? I was thinking you could have a public set and private get - but is that even possible or is there a way to read a value through the process of setting it?
@Griffork How would you implement a property that can set from the outside, but that can be modified inside of the class? I think the only viable solution right now would be to shadow that property with a private field. Which adds a lot of noise to the code.
@yvbeek You can't, but this proposal would enable it. Remember that there's runtime no implementation difference between a variable that's readonly and a variable that's not. Both can be set at runtime, it's just a matter of how the compiler responds to it. If we're splitting get and set's private/protected/public access level I see no reason why set can't have a more accessible level than get.
Unless there is a way to read the value of the variable via setting it (e.g. by setting it to itself) that would not be able to be detected and marked as an error by the compiler.
I see no reason why set can't have a more accessible level than get.
@Griffork How about nonsensical design? Here it is more a question of semantic rather than just syntax, and it was discussed before:
...public accessibility must be a strict subset of protected accessibility, which in turn must be a strict subset of private accessibility
Currently when you write public a
it is implied that both protected and private scopes inherit the same accessibility. Write protected a
and now only the private scope has the same access. Allowing something to be settable from public scope but not protected nor private doesn't seem to make much sense. Because we can doesn't mean we must.