Typescript: [feature] class properties that are "readonly on the outside, writable on the inside"

Created on 20 Mar 2020  路  13Comments  路  Source: microsoft/TypeScript

Search Terms

typescript readonly on the outside writable on the inside

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.
Awaiting More Feedback Suggestion

Most helpful comment

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.

All 13 comments

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 outsideof the class, and writable only inside the class (and subclasses too?)

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.

  1. the compiler would still allow me to create an instance of Foo and then set the foo variable
  2. this forces the developer to use interfaces instead of classes
  3. 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;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  路  3Comments

zhuravlikjb picture zhuravlikjb  路  3Comments

weswigham picture weswigham  路  3Comments

bgrieder picture bgrieder  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments