-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Feature Request: Readonly<T> should remove mutable methods #35313
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
Comments
How does the compiler know which methods are mutative and which ones aren’t? |
Remove all the methods! |
It knows because you mark the non mutating methods. class Foo {
private state = 1;
mutateState() {
this.state += 1;
}
readonly getState() {
// Inside a readonly method this is treated as Readonly<this> which will mark all
// properties as readonly and only all you to call readonly methods.
return this.state;
}
} Don't worry about the syntax at the moment. I don't know what would be best for that. Right now I just want to discuss the idea. |
It’s a good idea, but I’d expect it to work something like “const correctness” in C/C++—that is, non-mutating methods should not be able to call anything mutative, and this should be verified by the compiler. Otherwise the annotation (whatever it ends up being) has no teeth and would be easy to get wrong. |
I could have a go at making a PR if that would be useful? |
I've been experimenting with some code and I've refined my pattern some more. So the following is how it works today: export class Rectangle {
public constructor(
private width: number,
private height: number,
) {}
public getArea(this: InternalReadonlyRectangle): number {
return this.width * this.height;
}
public setWidth(value: number): void {
this.width = value;
}
public setHeight(value: number): void {
this.height = value;
}
}
interface InternalReadonlyRectangle {
readonly width: number;
readonly height: number;
getArea(): number;
}
export interface ReadonlyRectangle {
getArea(): number;
} This is not bad, but it does leave some things to be desired.
I would like to be able to write this: class Rectangle {
public constructor(
private width: number,
private height: number,
) {}
public getArea(this: Readonly<this>): number {
return this.width * this.height;
}
// Some syntax ideas:
// readonly public getArea(): number
// const public getArea(): number
// public getArea(this as const): number
// Rust style:
// public getArea(Readonly<this>): number
// Or just what we have today. I'm not too bothered really.
public setWidth(value: number): void {
this.width = value;
}
public setHeight(value: number): void {
this.height = value;
}
} |
Two more things that I have either noticed, or thought about:
|
Note method(this: Readonly) won't work across inheritance. class Foo {
x: number = 0;
test(this: Readonly<Foo>){
// this.x = 5; will raise error.
console.log(x);
}
}
class Bar extends Foo {
test(){
this.x = 5; // Compiler won't complain.
}
} |
https://stackoverflow.com/a/751783 It may be worth thinking about the idea of Also, I don't think this would be a breaking change if the |
@OldStarchy Using What I mean by that is: class Thing {
private value = { prop: { inner: 0 } };
setValue(newValue: number): void {
this.value.prop.inner = newValue;
}
const getValue(): number {
// This is an error because the method is marked as const so this.value has type { readonly prop: { readonly inner: number } }.
// Similar to how `as const` acts on object literals.
this.value.prop.inner = 15;
return this.value.prop.inner;
}
} |
While I like this idea, it would break backwards compatibility pretty badly. Currently A backwards-compatible alternative is that instead of marking non-mutating methods as |
My proposal is backwards compatible because marking a method as What you've proposed wouldn't be backwards compatible since it would make all methods Your proposed change would make the following an error: class Something {
private counter: number;
public doThing(): void {
this.counter += 1;
// ^^^^^^^^^^^^^^^^^
// Error: Can't mutable `this.counter` in non-mutable method `doThing`.
// Mutable methods must be marked as `mutable`.
}
} The above would have to be the case otherwise there would be no guarantee that a plain method (i.e. one not marked as const a: Readonly<Something> = new Something();
a.doThing();
// There would be no error here, even though there should be, since `doThing` is not
// marked `mutable` and therefore not remove from `Readonly<Something>`. Personally, I would prefer immutable by default, but it would cause a lot of headaches to change retroactively. |
Uh oh!
There was an error while loading. Please reload this page.
Search Terms
readonly, as const, Readonly,
Suggestion
Readonly to work the way Readonly<T[]> does without having to create something like ReadonlyArray, ReadonlySet, etc...
I would love it if the
Readonly<T>
mapped type could exclude any method marked asreadonly
or maybeas cosnt
or something similar.Use Cases
At the moment I have to do this:
This gets very boring with a lot of types, also it's error prone. I could forget to add something to the interface, or worse I could accidentally add something that is mutable.
I can currently do
method(this: Readonly<this>) {...}
, which is helpful, but doesn't stop me calling mutable methods on the type.Inside a marked method you would be unable to mutate the state of member variables and you would unable to call other unmarked methods.
This would also allow you to remove the special case of
Readonly<T[]>
. The mutable methods of arrays could be marked with whatever syntax is decided and thenReadonly<T[]>
would just work like any otherReadonly<T>
. Currently I don't bother usingReadonly<T>
since it doesn't really help with anything except C style POD types.Examples
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: