Typescript: Suggestion: read-only modifier

Created on 15 Jul 2014  Â·  132Comments  Â·  Source: microsoft/TypeScript

Some properties in JavaScript are actually read-only, i.e. writes to them either fail silently or cause an exception. These should be modelable in TypeScript.

Previous attempts to design this have run into problems. A brief exploration:

interface Point {
    x: number;
    y: number;
}
interface ImmutablePoint {
    readonly x: number;
    readonly y: number;
}
var pt: ImmutablePoint = { x: 4, y: 5 }; // OK, can convert mutable to non-mutable
pt.x = 5; // Error, 'pt.x' is not a valid target of assignment

var pt2: Point = pt; // Error, cannot convert readonly 'x' to mutable 'x'

// Possibly bad behavior
var pt3: Point = { x: 1, y: 1 };
var pt4: ImmutablePoint = pt3; // OK
pt3.x = 5; // pt4.x is also changed?

// Really bad behavior
/** This function was written in TypeScript 1.0 **/
function magnitudeSquared(v: { x: number; y: number }) {
   return v.x * v.x + v.y * v.y;
}
// Now try to use it with ImmutablePoint
console.log(magnitudeSquared(pt)); // Error, cannot use readonly object in non-readonly call

Possible solutions?

  • Allow bivariance of mutability: this is very unsound
  • Something else clever? C++ did not do well with const contamination
Committed Fixed Suggestion

Most helpful comment

:+1:

All 132 comments

I think this is probably the most important feature in terms of making Typescript a safer language. E.g. You are guaranteed that the object will not be mutated along the way and that simplifies reasoning a lot.

however, I don't see the problem with magnitudeSquared, it does not change 'v', so, it should work fine with both read-only and mutable objects.

:+1:

I don't see the problem with magnitudeSquared, it does not change 'v',

The problem is, you don't know this without reading the function implementation, and you might not have access to the implementation. Let's say someone gives you their library with a 1.0 TypeScript definition file:

// Docs say: calculates the squared magnitude of a vector
declare function magnitudeSquared(v: { x: number; y: number }): number;
// Docs say: rescales a vector to have length 1 and returns
// the scale factor used to normalize the vector
declare function normalize(v: { x: number; y: number }): number;

One of these functions modifies the object, one of them doesn't. You can't tell by looking at their signatures -- they're identical.

Now we have two obvious options

Assume all functions arguments are actually treated as read-only?

That fails to catch this problem

var p: ImmutablePoint = { x: 1, y: 3 };
var pN = normalize(p); // Failure to catch immutability violation

Worse, now you have to _opt in_ to non-readonliness. When you upgrade your definition file to have the read/write information, now you're going to have to write mutable basically everywhere:

// I have to write the word 'mutable' this many times?
declare function normalize(v: { mutable x: number; mutable y: number }): number;
declare function invert(v: { mutable x: number; mutable y: number, mutable z: number }):void;
declare function changeRotation(v: { mutable theta: number; mutable phi: number }): void;

Assume all function arguments are actually mutable?

Now I can't use magnitudeSquared without a cast:

var p: ImmutablePoint = { x: 1, y: 3 };
var len = magnitudeSquared(p); // Error, this function might modify p!

And you get to write readonly everywhere:

// I have to write the word 'readonly' this many times?
declare function magnitudeSquared(v: { readonly x: number; readonly y: number }): number;
declare function length(v: { readonly x: number; readonly y: number, readonly z: number }): number;
declare function dotproduct(v: { readonly theta: number; readonly phi: number }): number;

Can this be re-opened as a request? Given the increased use of functional programming with JavaScript and immutable data structures (see React and Flux). I think this would be great as an opt-in feature, maybe with a top-level decorator enabling it to avoid the issues of default behavior (i.e. ignore the check by default).

I'm not sure what you're asking? The issue is currently open.

Sorry, misread the status.

+1 for readonly keyword support.

+1 for something along these lines. The problems described by @RyanCavanaugh are quite real but the suggestion by @joewood to have some sort of decorator seems to be a smart one. I guess it might be along the lines of 'use strict' at the start of functions but probably more explicit, like
mutable class vs immutable class
and
mutable module vs immutable module
to specify the defaults within a block of code at the module or class level. For back-compat reasons, mutability would have to be assumed, right?

Perhaps along the same lines, where ? is used on a parameter to indicate it's optional, could other annotations be added, along with one indicating either (like how any throws away types)?

"The perfect is the enemy of the good." As TypeScript stands today, the compiler won't even tell me when I set a property with no setter -- with the full implementation available. This has been raised as an issue multiple times, and those other issues all point back to this one, so it seems pretty important that progress be made in this area. To me, having the readonly decorator and catching the assignment-with-no-setter error would be a huge step in the right direction, even if it doesn't solve the third-party library problem.

Edit: Removed reference to readonly decorator. Readonly is a tar pit, and my point is around get/set, not interfaces.

+1 for a readonly modifier or similar. The scenario I am frequently running into is wanting to define an interface with properties that only requires a getter, which I would then expect the compiler to enforce by generating an error if the code tries to set a readonly property. Implementations would be free to provide a setter if they choose to do so, but it should only be usable in code if the object is being referenced as the implementing class or a derivative of it.

My personal preference would be the way c# does it, allowing you to specify get and/or set in the interface, but the readonly case is by far the most useful (other than the full get/set case).

Thinking out loud here:

While readonly fields are important, perhaps an easier thing to implement, which doesn't have some of the problems @RyanCavanaugh outlined is immutable classes/interfaces.

Suppose you have:

immutable interface ImmutablePoint {
    x: number;
    y: number;
}

Given the properties of an imutable object, we can change how assignment behaves, to instead clone and freeze in JS.

So that:

var pt3: Point = { x: 1, y: 1 };
var pt4: ImmutablePoint = pt3; // OK -- pt3 is copied
pt3.x = 5; // pt4.x -- does not change

The pt4 assignment would basically look like:

...
// compiled JavaScript
var pt4 = pt3.clone(); // your favorite clone() scheme
pt4.freeze();
...

The compiler can then optimize the clone & freeze into only a freeze if:
1- we are using a literal assignment
2- possibly, if the variable/value we are copying is dead after this line (when dealing with global scope ,etc.)

Some problems remain with this:

  1. Unclear what to do about deep properties; I'm tempted to say "nothing" here... the immutability guarantee might be seen as a guarantee to not change references to other objects (values of pointers), rather than values within these objects.
  2. For the magnitudeSquared problem, we'd need to declare it as magnitudeSquared(v: immutable v { x: number, y: number }).

I was actually thinking of something more like the const modifier in C++. This would be applied to the object instance, providing an immutable view of an existing object. So something more like:

interface Foo {
    x : number;
    getConstBar() const : const Bar;
    getBar() : Bar;
}

function doNotMutate( obj : const Foo ) {
   obj.x = 3; // !! compiler error
   var bar = obj.getBar(); // !! compiler error, cannot call a non-const function 
   var constBar = obj.getConstBar(); // OK, can invoke a const function now have a const Bar
}

function mutate( obj: Foo ) {
    obj.x = 3; // works
    var bar = obj.getBar();  // works too
}

This way it's an opt-in on the interface, and can be passed down to child objects through accessor functions (or getters) that are declared const. Declaring a function const essentially applies the modifier to this (again, in the same way as C++ works).

So, in practical terms for libraries like react.js, the setState function would be mutable but the accessor functions to state and props would be immutable.

I prefer Eyas' suggestion. It's clean and simple. JavaScript has so many ways to modify state: adding/removing/deleting properties, side-effecting getters, proxies, etc. that introducing a concept like const will necessarily be horribly complicated.

There's also the issue of claiming that a method or argument in a JS library is const when it turns out not to be; there's no way to check it and not even runtime errors will be generated unless the compiler wraps arguments in a read-only proxy.

Perhaps even more explicit, another suggestion similar to my previous one:

No clone & freeze, simply, immutable {...} can only be assigned by types that are:

  1. Structurally compatible, and
  2. Also immutable.

A toImmutable() on every object converting object to immutable object is created. Which basically has clone and freeze symantics. Thus:

var pt3: Point = { x: 1, y: 1 };
var pt4: ImmutablePoint = pt3; // NOT OK -- Point and ImmutablePoint are incompatible
var pt5: ImmutablePoint = pt3.toImmutable(); // OK, structurally compatible
pt3.x = 5; // pt4.x -- does not change

The call .toImmutable(); basically implements clone and freeze.

The type definition for freeze() could also be augmented.. not sure how yet.

:+1: I really like Eyas' suggestion, but the .toImmutable(); function should not be part of TypeScript.

I've summarized some of my thoughts on immutables in TypeScript. May be this could be help.

// Primitives
val x = 7, y = 'Hello World', z = true; // ok -> all primitives are immutable by default

// Objects 
val p1 = { x: 120, y: 60 }; // ok -> all attributes are immutables

p1.x = 45; // error -> p1 is immutable
p1['y'] = 350; // error -> p1 is immutable

var p2 = { x: 46, y: 20 };
p1 = p2; // error -> p1 is immutable

val keyValue = { key: 'position', value: p2 }; // error -> p2 is not immutable

// Arrays
val numbers = [0, 1, 2, 4]; // ok -> all elements are immutable

numbers.push(9); // error -> numbers is immutable

val points = [p1, p2]; // error -> p2 is not immutable

// Functions
type Point2D = { x : number, y : number };

function multiply(s : number, val point : Point2D) {
    return val { x: s * point.x, y: s * point.y };
}

multiply(2, { x: 32, y: 20 }); // ok -> { x: 32, y: 20 } is immutable

multiply(2, p1); // ok -> p1 is immutable

multiply(3, p2); // error -> p2 is not immutable

function multiply2(s : number, val point : Point2D) : val Point2D {
    return { x: s * point.x, y: s * point.y };
}

// Type Assertion
val p3 = <val Point2D>p2;

// Classes
class Circle {

    private x: number;
    private y: number;
    private val r: number;

    constructor(x : number, y : number, r : number) {
        this.x = x;
        this.y = y;
        this.r = r; // ok -> in constructor
    }

    setRadius(r: number) {
        this.r = r; // error -> this.r is immutable
    }
}

var circle = new Circle(100, 200, 20);
circle.x = 150; // ok -> circle.x is not immutable

class Circle2 {
    constructor(private val x : number, private val y : number, private val r : number) { }
}

I am very strongly in favor of a conservative approach here. Probably the simplest would be allowing const for properties in declarations:

interface Point {
    const x: number;
    const y: number;
}

var origin: Point = {
    x: 0,
    y: 0,
}

The semantics of const here would be the same as if it were a binding: no assignments are allowed. Nothing else would be implied (the C++ const semantics would get hairy pretty quickly).

That is, the following is allowed:

interface Foo {
    const map: { [key: string]: string };
}


var x: Foo = { map: {} };
x.map['asdf'] = 'hjkl';

The compiler should assume all properties are mutable unless told otherwise. This sucks for people who like to use objects in a read-only fashion (myself included), but it tracks the underlying JavaScript better.

I can think of at least four kinds of immutability in JS:

  1. const bindings, as offered by ES6,
  2. Get-only computed properties,
  3. Ordinary properties configured with writable: false in their descriptors, and
  4. Objects passed through Object.freeze.

From a type perspective, numbers 2 and 3 act the same, but the machinery is technically different. I see some comments above about annotating an entire object as immutable, which wanders into the territory of point 4, but I think that opens a can of worms. (More below.)

I thought about giving a rough spec, but here's a fun question:

Given:

interface Foo {
  const x: number;
}

interface Bar {
   x: number;
}

var x: Foo =  { x: 1 },
    y: Bar = { x: 1 };

Is the following assignment compatible?

var z: Foo = y;

That is, can we allow mutable properties to "downgrade" into immutable variations? It makes sense in function scenarios:

function showPoint(point: { const x: number; const y: number }): string {
    return "(" + point.x + ", " + point.y + ")";
}

console.log(showPoint(x), showPoint(y));

While (rather) confusing, it does have the advantage (?) of jiving with what JavaScript offers with get-only properties:

var x = 0;
var obj = {
    get x() {
        return x++;
    }
};

console.log(obj.x, obj.x);

Despite the question above, I would suggest behavior like the following: the presence of const in a property declaration is treated by the compiler as a read-only value; any and all assignments are banned. In the presence of const properties, assignments are compatible if the compiler can verify that the property is readable:

interface Foo {
    const x: number;
}

var x: Foo = { x: 1 }; // okay
var y: Foo = { get x(): { return 1; } }; // also okay, because 'x' is readable
var z: Foo = { set x(_x): { return; } }; // no, 'x' has no getter

Compilation output for a class would be roughly as follows:

class Foo {
    const x = 1;
}
var Foo = (function () {
    function Foo() {
        Object.defineProperty(this, "x", {
            value: 1,
            enumerable: true,
            configurable: true,
            writable: false,
        });
    }
    return Foo;
})();

Not sure how it would compile for a module. Perhaps it would use Object.defineProperty on the module object or instead try to align with ES6?

Declaring a function as const would have the possibly counterintuitive effect of making the _function declaration_ immutable, not its return value.

That is, given the following:

interface Math {
    const sin(x: number): number;
}

Any instance of Math can't have its sine implementation replaced. (But users could say (<any> Math).sin = /* ... */ if they wanted.)

This can cause issues with declaration merging, though.

interface Foo {
    const func(): void;
}

interface Foo {
    func(x: number): void;
}

In the case above, have we a) implicitly added a setter for the property func, or b) removed immutability? If the meaning is a, then we're okay. If it's b, then we should error, because the second interface has discarded a previously-existing guarantee.

My gut says we should go with interpretation b, given the intent we're trying to express. And if that's the case, then perhaps there should be interface syntax for getter and setter properties so that the following _won't_ be an error:

interface Foo {
    get x(): number;
}

interface Foo {
    set x(_x: number): void;
}

As opposed to above, the intent here is not immutability but the allowed operations on a property.

As an aside, I'm on the fence about allowing const on indexers:

interface Whitelist<T> {
    const [key: string]: T;
}

Perhaps it's best to leave it off for now and let it come into existence if people ask for it.

With all of this in hand, the notion of an immutable object _seems_ easy: add some keyword (perhaps frozen to match the underlying Object.freeze, or immutable for obvious reasons) that means "all properties on this object are const.

But then you have the unenviable position of figuring out if a method is safe to call on an immutable interface:

immutable interface Vector {
    x: number;
    y: number;
    magnitude(): number; // safe
    normalize(): void; // not safe
}

It's not really enough to look at void return types, though:

var x = {
    x: number;
    y: number;
    debug() {
        console.debug("vector: ", this.x, ",", this.y);
    }
};

It's clearly safe to call x.debug() even if it were of an immutable type because no property is modified. But I shudder to think of what its return type should look like. const void?

So to make a long reply short (too late), we could add const as a modifier to property declarations to indicate that the compiler should prevent the user from mutating them. It's difficult to make other guarantees.

There is also an EcmaScript 7 proposal on immutable data structures: https://github.com/sebmarkbage/ecmascript-immutable-data-structures

To add to that, keep in mind that const exists in ES6. We should try to avoid overloading keywords that are going to have a specific meaning in future ES standards.

I'm not sure the immutable data structures proposal solves the problem of how to annotate that some properties can't be modified. They're two separate issues.

(I'm not married to the const usage I invented. It was mostly to get the highlighter to work.)

I guess with first class immutable data structures the type system will need some way to signify immutability . This will allow the compiler to do compile time checks.

I think that the readonly keyword might be useful for private and static functions to allow for better minification of generated files. For example, I have AMD code like the following:

export class MyClass{
    public myFunction() : number {
        return privateFunc(3);
    }
    private readonly privateFunc(toSquare: number): number{
        return toSquare * toSquare;
    }
}

It would be more efficient minification-wise if the code were treated as follows and then generated:

function privateFunc(toSquare: number): number {
    return toSquare * toSquare;
}
export class MyClass{
    public myFunction() : number {
        return privateFunc(3);
    }
}

In the first block, "privateFunc" cannot be minified down to "a" while the second block it can. In which case the readonly keyword would be used to let the compiler know to make sure that my private/static function calls are only to other private/static functions and not to instance variables and that nowhere in the class is there code which does this.privateFunc = newFunction

I currently write code according to the second block, but only when I the private/static function follows the constraints listed in the prior paragraph. I would much rather have the compiler take care of that refactoring for me.

Thoughts?

I've tried to summarize some of the thoughts from the above thread. I am quite new to TypeScript (and JavaScript as well) - so, apologies if some of the suggestions are infeasible for one reason or another - however, I think that, at least from the specs perspective, the below could work:

Declarations

interface Point {
    x: number,
    y: number
}

// declaring readonly properties is done with #
interface ImmutablePoint {
    #x: number,
    #y: number
}

Assignments

var p1: Point = { x: 1, y: 1 }; // p1 is fully mutable 
var p2: ImmutablePoint = { x: 2, y: 2 }; // underlying object is mutable
var p3: ImmutablePoint = #{ x: 3, y: 3 }; // underling object is not mutable

To illustrate the differences between the above:

p1.x = 100; // OK
p2.x = 100; // compiler error
p3.x = 100; // compiler error

var p4: ImmutablePoint = p1; // mutable objects can be cast down into immutable objects
p4.x = 100; // compiler error
p1.x = 100; // however, underlying object remains mutable and now p4.x is = 100

var p5: ImmutablePoint = #p1; // this will clone and freeze p1
p5.x = 100; // compile error
p1.x = 200; // does not affect p5.x

var p6: Point = p2; // compiler error
var p6: Point = <Point> p2; // immutable objects can be explicitly cast into mutable objects
p6.x = 100; // OK - object underlying p2 is mutable

// but
var p7: Point = <Point> p3; // OK - no way to check that object underlying p3 is not mutable
p7.x = 100; // runtime error - object underlying p3 is not mutable

// also
var p8: Point = #{ x: 1, y: 1 }; // compiler error

Function Calls

By default, function arguments are assumed to be mutable

function foo(v: { x: number; y: number }) {
    v.x = 100;
    return v.x * v.y;
}

foo(p1); // OK
foo(p2); // compiler error
foo(<Point> p2); // OK
foo(p3); // compiler error
foo(<Point> p3); // runtime error

but we can also explicitly make them immutable:

function foo(v: { #x: number; #y: number }) {
    v.x = 100; // compiler error
    return v.x * v.y;
}

foo(p1); // OK
foo(p2); // OK
foo(p3); // OK

There's a problem with allowing mutable objects to be accepted when a function expects an immutable object. Here is a slightly convoluted but illustrative:

var p1: Point = { x: 1, y: 1 }; // p1 is fully mutable 
function foo(v: {#x: number; #y: number}): (factor:number)=>number {
    return (factor:number) => (v.x+v.y) * factor;
}
var bar: (f:number)=>number = foo(p1);
var v1 = bar(1); // (1+1)*1=2
p2.x = 2; // legal because p1 is immutable
var v2 = bar(1); // (2+1)*1=3
// v1 !== v2

I think this is a strange behavior. foo was written with the explicit intention of having the variable v, which backs bar (thanks to closures) be immutable.

I can see how this behavior is undesirable. I think there are 2 potentially conflicting use cases for read-only properties here.

Use case 1: underlying data for a read-only property should never change (your example above)
Use case 2: underlying data for a read-only property could change, but not through the exposed interface. To illustrate

interface IPerson {
    firstName: string,
    lastName: string,
    #fullName: string // this property is read only
}

class Person implements IPerson {
    constructor (public firstName: string, public lastName: string) { }
    get fullName(): string {
        return this.firstName + ' ' + this.lastName;
    }
}

var person = new Person('a', 'b'); // person.fullName is now 'a b'
person.firstName = 'c'; // OK - firstName is mutable and person.fullName is now 'c b'
person.fullName = 'x y'; // compiler error

Naturally, there are 3 options to handle this:

(1) sacrifice use case 1 in favor of use case 2
(2) sacrifice use case 2 in favor of use case 1
(3) somehow accommodate both use cases

To take a stab at the 3rd option: what if we have 2 different ways to define immutable parameters in function signature? specifically:

function foo(p: IPerson) {...}
function bar(p: #IPerson) {...}

var person = new Person('a', 'b');
foo(person); // OK - foo is able to modify mutable properties of person
bar(person); // OK - bar gets a cloned&frozen copy of person - no properties can be modified

After thinking about this some more, it seems that the distinction between "read-only interface" and "read-only data" may be 2 different issues and could/should be addressed separately.

read-only interface

This is when the data cannot be modified through the exposed interface, but may or may not be modifiable through other interfaces. To illustrate:

interface Person {
    firstName: string;
    lastName: string;
    #fullName: string;
}

interface ImmutablePerson {
    #firstName: string;
    #lastName: string;
    #fullName: string;
}

class Person1 implements Person {
    constructor (public firstName: string, public lastName: string) { }
    get fullName(): string {
        return this.firstName + ' ' + this.lastName;
    }
}

class Person2 implements Person {
    constructor (public firstName: string, public lastName: string) { }
    get fullName(): string {
        return this.firstName + ' ' + this.lastName;
    }
    set fullName(value: string) {
        // parse value and set this.firstName and this.lastName
    }
}

// the above should result in the following behavior
var p1 = new Person1('a', 'b');
var p2 = new Person2('x', 'y');

p1.firstName = 'q'; // OK - and now p1.fullName is = 'q b'
p2.firstName = 'y'; // OK - and now p2.fullName is = 'y z' 

p1.fullName = 'c d'; // compiler error
p2.fullName = 'x z'; // OK - and now p2.firstName is = 'x' and p2.lastName is = 'z'

var p3: Person = p2; // OK - implicit cast of mutable to immutable
p3.fullName = 'c d'; // compiler error - Person.fullName is read-only
p2.fullName = 'i k'; // OK - and now p3.fullName returns 'i k';

var p4: ImmutablePerson = p1; // OK - implicit cast of mutable to immutable
p4.firstName = 'n'; // compiler error - ImmutablePerson.firstName is read-only

in functions

function foo1(p: Person) { ... }
function foo2(p: ImmutablePerson) { ... }
function foo3(p: { firstName: string, lastName: string, fullName: string } ) { ... }

var p1 = new Person1('a', 'b');
var p2 = new Person2('x', 'y');
var p3: ImmutablePerson = p1;

foo1(p1); // OK
foo2(p1); // OK - implicit cast from mutable to immutable
foo3(p1); // compiler error - Person1.fullName is read-only

foo1(p2); // OK
foo2(p2); // OK - implicit cast from mutable to immutable
foo3(p2); // OK

foo1(p3); // compiler error - ImmutablePerson.firstName is read-only
foo2(p3); // OK
foo3(p3); // compiler error - ImmutablePerson.firstName is read-only

// but
foo1(<Person> p3); // OK - explicit cast from immutable to mutable

I think the above behavior could be achieved with a few relatively simple rules:

(1) If a property in an interface is declared as read-only: attempts to assign value to this property via this interface should result in compile errors
(2) A read/write property can be implicitly cast into a read-only property
(3) A read-only property can be explicitly cast into a read/write property

read-only data

I think this is conceptually different from the above - and it relies more on what happens at runtime - rather than at compile time. Basically, the data cannot be modified through any interface. The approach could be as follows:

interface Person {
    firstName: string;
    lastName: string;
    #fullName: string;
}

interface ImmutablePerson {
    #firstName: string;
    #lastName: string;
    #fullName: string;
}

class Person1 implements Person {
    constructor (public firstName: string, public lastName: string) { }
    get fullName(): string {
        return this.firstName + ' ' + this.lastName;
    }
}

var p1: Person = new Person1('a', 'b');
var p2: #ImmutablePerson = p1; // p1 is cloned and frozen and the resulting object is assigned to p2

Basically, putting # in front of a type indicates that an object should be clones and frozen on assignment. The above could be also done as:

var p2 = #p1; // p1 is clones and frozen, and type of p2 now is equivalent to ImmutablePerson

This could also work in function signatures

function foo(p: #{firstName: string, lastName: string, fullName: string }) {
    p.firstName = 'a'; // compiler error
}

var p1: Person = new Person1('a', 'b');
foo(p1); // OK - foo now has a frozen copy of p1 and altering p1 does not have any effect on argument p inside foo

Readonly should be implemented more gradually, in parts where it's known to not cause problems such as:

function something(readonly myvalue) {
    myvalue.something = 5; // Compiler error
}

It would still be useful. In my current case I would like to define it in the interface method param.

interface Some {
    something(readonly myvalue); 
}

To make sure the implementing classes does not modify the value when called with something.

P.S. There is something peculiar with readonly parameters, cause it's easier to think that they always are readonly and only intention to mutate parameters should be written. E.g. function something(mutable myvalue), but this would require to add readonly implicitely to all parameters and explicitely to define intent to mutate parameters.

:+1:

Can we(actually you :D) just add the getter only property in interface first? I really want it, very badly!
For the compatibility issue, I agree with the idea of "use strict" thing. And maybe a writable keyword for calling legacy code from strict mode.

strict {
    var pt:ImmutablePoint = { x: 100, y: 200};
    legacyFunction(writable pt);
}

Bump...this would become even more useful if (and maybe when) non-numeric enums become part of TS. The typical use case for enums involve zero state, just data.

:+1:

+1

+1

I would already be very happy if a simple compiler warning could be generated when trying to assign something to a property without a setter.
I was refactoring by encapsulating fields into properties with only getters and forgot to change some references. (In hindsight I should have done a refactor -> rename and add an underscore to the field before changing it to private and exposing the getter).
Silently failing is pretty bad if it's easy to do unintentionally :/

I agree with @drake7707, this was my main reason to start following this thread.

:+1: for @drake7707's idea; also the reason I am following this thread.

@drake7707 Here's the test from my quick shot at implementing this feature as I see it: https://github.com/joelday/TypeScript/blob/getSetKeywordAssignment/tests/cases/compiler/assignmentToReadOnlyMembers.ts

Basically, if you put "get" before an interface member or if a class "get" accessor property doesn't have a matching "set" accessor, assignment is not allowed. It probably has some holes and I could see "readonly" being a better keyword. I would very much appreciate seeing this polished up and included in the language.

In any case, as for the long history of this issue, (it's 12 out of 692) this isn't about the deep implications of immutability language features, it's about an extremely simple guard against assignment, either deliberately or when we can trivially detect, 100% for sure that the assignment will fail at runtime.

Looks good to me, the only thing that I could think of is the following:

class A {
    protected _value:string = "initial";
    get value():string { return this._value; }
}

class B extends A {
    set value(val:string) { this._value = val; }
}

var b = new B();
b.value = "test";

alert(b.value);

I don't even know if this should be allowed, in any case, b.value is undefined

I commented in this issue because lots of issues got closed that pointed this issue out referring to this issue as root cause.

I'd like to second what was said above, readonly is totally not about immutability - you can have a readonly property without immutability, it only means you're not allowed to write to it, not that its value can't change. It's neither about complex typesystem features - the only thing that would really matter to our team would be a readonly keyword that emits errors on obvious attempts to write to a readonly property. For us it's ok if casting or otherwise losing type information breaks these guarantees, whatever, we still have implicit anys all over the place.

@drake7707 That's a perfectly good case. I don't think my class member scan is looking at inherited tree, so I should fix that. I think it would probably be useful to add support for the "set" keyword on interfaces to support the scenario where an extending interface wants to add writability. It probably makes sense to have an error when a set-only member is being read from, but nobody really uses write-only properties as far as I've ever seen.

@hdachev Thanks! The problem I see with how issues have been duped and closed to this issue is that simple assignment guarding has been conflated with the entire question of what immutability features should/shouldn't exist. Maybe there's a clever approach that satisfies this scenario in a better way, but in any case, I think it's reasonable to assume that most developers want the equivalent of "string Foo { get; }" in C#, despite the possibility that people focused on language design for this project might have an aversion to emulating a C# feature just because people want it.

This appears to be aligned with ES6 "const" as I understand it, but for members rather than variables. I do want to make it clear that I don't think get-only members should const implicitly when assigned to a variable. If people want more immutability than this, they can take the responsibility to do it through the deliberate use of this feature on everything that should be immutable rather than adding magic that complicates the language.

TL;DR This should work like it does in C# because it's simple and it's what people have been asking for. Also, it only took a day to prototype it.

(Edit: const implies that the value itself is never a different value or object reference, though, which is not the case here, so I don't think that is a good keyword to use here. I'm leaning towards keeping "get" because then it makes sense for adding a "set" version to an extended interface.)

@joelday I love where you're going with this.

I agree that it's just a matter of only being able to read a value. Getters
without setters are readonly in JavaScript, generating a runtime error on
violation. Constants generate the same error. They both could be better
typed with read only support.

And many JavaScript libraries use this as well. It would benefit definition
writers as well.

On Mon, Aug 24, 2015, 16:57 Joel Day [email protected] wrote:

@drake7707 https://github.com/drake7707 That's a perfectly good case. I
don't think my class member scan is looking at inherited tree, so I should
fix that. I think it would probably be useful to add support for the "set"
keyword on interfaces to support the scenario where an extending interface
wants to add writability. It probably makes sense to have an error when a
set-only member is being read from, but nobody really uses write-only
properties as far as I've ever seen.

@hdachev https://github.com/hdachev Thanks! The problem I see with how
issues have been duped and closed to this issue is that simple assignment
guarding has been conflated with the entire question of what immutability
features should/shouldn't exist. Maybe there's a clever approach that
satisfies this scenario in a better way, but in any case, I think it's
reasonable to assume that most developers want the equivalent of "string
Foo { get; }" in C#, despite the possibility that people focused on
language design for this project might have an aversion to emulate a C#
feature just because people want it.

This appears to be aligned with ES6 "const" as I understand it, but for
members rather than variables. I do want to make it clear that I don't
think get-only members should const implicitly when assigned to a variable.
If people want more immutability than that, they can take the
responsibility to do it through the deliberate use of this feature on
everything that should be immutable.

TL;DR This should work like it does in C# because it's simple and it's
what people have been asking for. Also, it only took a day to prototype it.

—
Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/12#issuecomment-134377631
.

@RandScullard & @impinball Glad we're on the same page. :D

Providing only getter does not guarantee the immutability of the property.

class A {
    private _value: string = 'initial';
    get value(): string {
        return this._value;
    }
    doSomething(): void  {
        this._value = 'another value'; // internal reassignment cannot be prevented.
    }
}

Although this issue is mainly about externally declaring object properties as immutable, I'm concerned with the immutability of the internal state (maybe I should create another issue).
I hope some keyword (such as final) could inhibit reassignment of instant variables as follows:

class A {
    private final str = 'str';
    private final num: number;
    constructor(private final bool: boolean) {
        this.num = 0;
    }

    doSomething() {
        // The below codes raise compilation errors
        this.str = '';
        this.num = 0;
        this.bool = false;
    }
}

The immutability of the internal state enables simple and side effect free programming.
I suppose this is an invaluable functionality based on my experience on the other OOP languages (such as Java and Scala).

Very common in C# too. ReSharper (and I think now VS) both recommend you turn once-set internal fields into readonly fields.

For me, as long as the immutability ensured by read-only or getter-only doesn't stop at object references like the final keyword does in Java, then it would be tremendously helpful.

@dsebastien
If an object publishes a method which alters its internal state, then what you mentioned does not help, I'm afraid.

someMethod(readonly myValue: string[]) {
    myValue.length = 0; // if my understanding is correct, you want invalidate this, right?
    myValue.push('str');  // even then, this can happen!
}

What really ensures object immutability is to make its internal state immutable, which is what Scala's immutable collections do, for example.

@kimamula I see what you mean but I guess it all depends on how far the TS compiler is willing to go :)

Like in C++, I think what is needed is a way to restrict the context to readonly. The compiler would then make sure that only immutable functions can be called on the context. So, something like:

someMethod(readonly myValue: string[]) readonly {
    myValue.length = 0; // error because myValue is readonly and this is a property set
    myValue.push('str'); // this would error because array.push is not marked as readonly (it mutates)
    this.xxx = 0; // this would also error because someMethod is readonly
    let x = myValue.length; // this would work - property get
    let xx = myValue.map( p => p + this.xxx); // this would work - map is a readonly function accepting a readonly lambda
}

Note that the compiler would need to promote a lambda to a readonly lambda if it contains no context mutating operations.

@joewood With that particular example, I'm reminded of C#/JetBrains [Pure] attribute. Would be very interesting to only allow calling "Pure" functions or getters on a variable marked readonly.

@joewood While bad practice, nothing prevents a user to implement a property getter that has side effects. (unless ofcourse you can also annotate its signature as readonly)

I think the compiler could warn on that. If the compiler auto promotes any function (including property getters) to read-only if they don't mutate then the compiler can warn if the readonly context modifier is applied and contradicts with what the compiler sees.

Of course, the problem with this approach will be like in C++ with const. One bad apple can spoil the whole thing.

Is it possible to add a check to compiler similar to "suppressImplicitAnyIndexErrors": true" that would check assignment to "get-only" properties? I think that is a good start that will cover 95% of its use cases. Immutability could be solved as a separate task. How about "checkReadonlyProperties"? I am probably reinventing hot water, but I think this would be extremely useful for many of TS programmers.

@tomitrescak That could just as easily be solved by marking getters as readonly.

@impinball And how can that be done? Specifying only getter and assigning value to such getter passes compilation.

Or at least a constant reference. Something to that effect.

@impinball you are being very confusing. How can you mark a getter as readonly so that assiging value to it will not pass compilation?

In another words, how do you make follwing fail compilation?

class MyClass {
    private theCount: number;
    private theOther: number;

    public get count() {
        return this.theCount;
    }

    public set other(value: number) {
        this.theOther = value;
    }
}

var c = new MyClass();
c.count = 5; // should fail

var e = c.other; // should fail

@tomitrescak I mean in the sense of it being a glorified constant.

const foo = 2;
foo = 1; // Error

class Foo {
  get foo() { return 1; }
}

const obj = new Foo();
obj.foo = 2; // Error

Well, that's not what I was proposing :/, but thanks for your insight! Sorry for hijacking the discussion.

Defining an object as a constant is a hack @impinball, I agree with @tomitrescak that assignments to getters should fail compilation. Same for reading a getter. Moreover, an interface should be able to enforce these constraints.

@gustavderdrache nailed that in comment: https://github.com/Microsoft/TypeScript/issues/12#issuecomment-78297192

I think the const obj creates confusion in the example @impinball. Instead, I imagine noone would disagree that this must hold true:

class Foo {
  get foo() { return 1; }
}

var obj = new Foo();
obj.foo = 2; // Error

Read-only properties have been a matter of fact in javascript since the beginning of time, and it's unambiguous that type system support for read-only properties will be immensely helpful. Any other discussions about const objects and immutable collections IMHO are not relevant to this discussion.

Once we have read-only properties we could start discussing seriously stuff like modelling javascript "frozenness" and "sealedness" formally, but all our arguing about everyone's expectations of immutability and purity here are only detracting from the discussion. I seriously suggest we focus on getting type system support for read-only properties and think immutability next thing.

Okay... I'll admit the similarity with const would be a little bit
hackish. But last time I checked, you can't assign to either one, const
or getter without setter. You can get the value of either one, but they
can't be assigned to. Because they behave identically on the left hand side
(excluding getter side effects), that's why I suggested it.

As for const obj vs var obj, I typed const out of habit. Any of the
three declarations would suffice.

On Fri, Sep 18, 2015, 05:26 Hristo Dachev [email protected] wrote:

I think the const obj creates confusion in the example @impinball
https://github.com/impinball. Instead, I imagine noone would disagree
that this must hold true:

class Foo {
get foo() { return 1; }
}
var obj = new Foo();
obj.foo = 2; // Error

Read-only properties have been a matter of fact in javascript since the
beginning of time, and it's unambiguous that type system support for
read-only properties will be immensely helpful. Any other discussions about
const objects and immutable collections IMHO are not relevant to this
discussion.

Once we have read-only properties we could start discussing seriously
stuff like modelling javascript "frozenness" and "sealedness" formally, but
all our arguing about everyone's expectations of immutability and purity
here are only detracting from the discussion. I seriously suggest we focus
on getting type system support for read-only properties and think
immutability next thing.

—
Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/12#issuecomment-141396042
.

This is now Typescript 1.6 available and still no support for this feature. You even implemented a support for third party library like the React in this version and still no long awaited and long discussed support for the compiler to check that the property has no setter. I had a bug in my project because the compiler did not give any hint while i assigned values to readonly properties.

Could you please make this feature in the compiler because it is needed for large projects like mine
https://github.com/BBGONE/jRIAppTS

I suggest a quick and effective solution.
As a compromise you can allow implicit casting immutable => mutable is OK.
Just don't allow assignment to a readonly property when the variable has type of the interface with that property marked readonly.
It is needed because developers who create frameworks define interfaces in it which can be consumed by clients and the clients should be aware what property is readonly and which is not.
It is up to them to follow the contract what the developers provided.
For example: an Entity can have calculated property and the clients should be aware that the property is readonly.
It will cover 99% of usecases. Very litle developers care about the issues that a function can accept an argument and modify it internally. Most of the problem is when you work with properly typed variables (with readonly properties) and there's an assignment to it in the code.

interface Point {
x: number;
y: number;
}
interface ImmutablePoint {
readonly x: number;
readonly y: number;
}
var pt: ImmutablePoint = { x: 4, y: 5 }; // OK, can convert mutable to non-mutable
pt.x = 5; // Error, 'pt.x' is not a valid target of assignment
var pt2: Point = pt; // immutable => mutable ALLOW IT!
pt2.x = 6; // ALLOW IT because it is now mutable
pt= pt2 //IT is again immutable
pt.x = 7; // Error, 'pt.x' is not a valid target of assignment

P.S. - reclassify this issue as a bug, because technically it is a bug : the compiler should check that a property can not be wriitten because a developer explicitly did not provide a setter.

@BBGONE

Only the part regarding getters without setters could remotely be considered a bug. The rest cannot, such as the readonly modifier for other types. The feature didn't exist before, and it's not directly mis-typing the language. And in case you're curious, even the mis-typing of this was considered a suggestion, not a bug. That problem came down to JavaScript being call-by-delegation in its object model, like Lua or Python, vs TypeScript typing it like a language with statically known methods like Java or C# (which isn't accurate at all).

@impinball
"even the mis-typing of this was considered a suggestion, not a bug."
It is considered a suggestion because it has an easy workaround and i use it often:
instead of untyped 'this' use a typed 'self'

function f() {
var self: SomeType = this;
self.x = 3;
}

But the issue with ReadOnly properties have no workaround.
And my proposal can easily be implemented transparently to the existing codebase.
Developers can then use interfaces with the new features, and their code will be protected from that bug if they use interfaces and typing consistently. Third party libs can be left as is.

@BBGONE

The thing with this is that even Function.prototype.call is improperly typed. The standard library doesn't correctly take this into account.

As for readonly properties, it's still a feature. It's not a feature that was improperly/incorrectly implemented. Those constitute actual bugs.

I really don't understand why this conversation is still going in circles. The spirit of the issue as a requested feature has nothing whatsoever to do with deep immutability. This is specifically nothing more than having a compiler error when we know at build time that, given the implicit or explicit interface of an object, an assignment is _absolutely guaranteed_ to always cause a runtime error.

I say this with full appreciation for the fact that syntax changes merit very serious scrutiny. I just propose that other immutability features should be forked to a different issue and considered OT here.

@joelday I agree with the entirety of your statement.

Back to the initial discussion, I do believe that there needs to be a way of marking whether something can be read or written. This would be an awesome thing to be able to have:

interface LodashArray {
  readonly length: number;
}

There's no way I can possibly modify the length without something going wrong. Also, assigning to getters without setters should also fail in this case:

class Foo {
  get foo() { return 2 }
}

new Foo().foo = 3 // error!
let foo = new Foo().foo // okay

Also, there should be a way to properly type setters without getters. I know it's not very common and not very elegant, but JS and TS both allow it, so it should be checked, anyways.

class Foo {
  set foo(value) { /* ... */ }
}

let foo = new Foo().foo // error!
new Foo().foo = 2 // okay

@jbondc You mean s/writable/readable/g and s/mutable/writable/g? ;)

Also, what about get T and set T? There is no existing ambiguity AFAIK, since there's just whitespace between the two tokens.

Ok. It really confused me. To be honest, I don't like that naming for that
very reason: it's non-obvious.

On Mon, Oct 19, 2015, 08:36 Jon [email protected] wrote:

@impinball https://github.com/impinball Yes well:

  • !writable reads as Not Writable (readable).
  • !mutable reads as Not Mutable (constant, immutable, ..?)

I explain it more here about attaching constraints to a type:

3076 (comment)

https://github.com/Microsoft/TypeScript/issues/3076#issuecomment-100675816

—
Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/12#issuecomment-149201689
.

I would read number!writable as "number that is writable", and number!writable!enumerable as "number that is writable and enumerable". Is that what you meant?

Couple of thoughts:

  • Wouldn't writable be better defined by simply the existence of the set property function? I realize that these are defined separately in JavaScript - but that makes more logical sense to me.
  • For other property metadata (like configurable and enumerable), rather than creating a new syntax couldn't decorators be repurposed for this? I mean using some well recognized decorators defined internally to the compiler?

Alternative I guess could be:

declare class Foo {
   someValue: number;
   foo: number{writable: false, enumerable: false}
}

@joewood Properties are writable in JS so not sure what you meant by the set property:

class Foo {
  someValue = 1
  set foo() { this.someValue++ }
}

The emit in a declaration file would be:

declare class Foo {
   someValue: number;
   foo: number;
}

With decorators, look at the generated code:
http://www.typescriptlang.org/Playground#src=%40A%0Aclass%20Clazz%20{%0A%20%20%20%20%40B%0A%20%20%20%20prop%20%3D%201%3B%0A%0A%20%20%20%20%40C%0A%20%20%20%20method%28%29%20{}%0A%0A%20%20%20%20%40D%0A%20%20%20%20get%20prop%28%29%20{return%201%3B}%0A%0A%20%20%20%20%40E%0A%20%20%20%20method2%28%29%20{}%0A%0A%20%20%20%20%40F%0A%20%20%20%20prop2%20%3D%201%3B%0A}

Not something I'm particularly excited about. They are a runtime thing and can't be applied to types/interfaces etc...

e.g.

function enumerable(proto, name) {
    Object.defineProperty(proto, name, { value: undefined, enumerable: true });
}

class Foo {
    @enumerable
    a: number;
}

The emit in a declaration file is:

declare function enumerable(proto: any, name: any): void;
declare class Foo {
    a: number;
}

So you lose the info.

@jbondc I mean if the emit included a declaration for the getter/setter. The compiler would then error if you were trying to use the property as an _lvalue_ in an expression. This seems more natural to me, unless I'm missing something.

For decorators - I'm talking about reusing the syntax as a way of adding metadata, not as a runtime evaluation or generating any code. So this would just be used for metadata exposed to the compiler.

So you mean:

class Foo {
  someValue = 1
  get foo() { return this.someValue }
}

Declaration is:

declare class Foo {
   someValue: number;
   get foo: number;
}

Guess that can work too, syntax is more a game of perspective.

Right, seems to be more inline with the goal of declaration files being the header files of JavaScript.

And this is why I suggested get and set modifiers (I don't care if it's
prefixing the type or value name - it reads the same). I think it would
make perfect sense. The intent is very clear in that you can get and/or
set the property.

// Prefixing the name
declare class Foo {
  someValue: number;
  get foo: number;
}

// Prefixing the type
declare class Foo {
  someValue: number;
  foo: get number;
}

I'm indifferent to which is used, although I suspect most here would prefer
the former.

On Mon, Oct 19, 2015 at 11:46 AM, Joe Wood [email protected] wrote:

Right, seems to be more inline with the goal of declaration files being
the header files of JavaScript.

—
Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/12#issuecomment-149254282
.

Isiah Meadows

Gotcha makes sense, like it.

@impinball Thanks! I think the issue with using "readonly" as a keyword in an interface is that it can be interpreted as declaring that the property cannot be writable on an object that implements that interface. Also, I think it would get confusing (or plain not work in some situations) when extending interfaces, and class declarations of get/set properties is also fundamentally additive rather than negative, so I think it would make sense for declaring it on interfaces to work the same way and use the same keywords.

Any progress?

+1 for final modifier.

Perhaps we can explore and borrow the idea of case classes from Scala
i.e.

immutable class Point {
    public x: number;
    public y: number;
}

where x and y are automatically readonly, all immutable classes are provided a copy() or clone() methods for deep and shallow copying

if a particular property needs to be mutable for any reason, simply denote it with var i.e. public var lastModified: Date

so that's +1 for immutable class for me

This discussion not about constants in classes, but some issues marked as duplicate of this, and I wont to show my vision about this (it also gives an option for this https://github.com/Microsoft/TypeScript/issues/3743 ).
We can do something like this:

class MyClass
{
    static const MY_STATIC_CONST: string = 'my staic const';
    const MY_CONST: string = 'my const';
}

And generated JS:

Object.defineProperty(
    MyClass,
    'MY_STATIC_CONST',
    {
        value: 'my staic const',
        enumerable: true
    }
);
Object.defineProperty(
    MyClass.prototype,
    'MY_CONST',
    {
        value: 'my const',
        enumerable: true
    }
);

It looks simple and useful for me.

Will we see a solution of this implemented in typescript compiler?
P.S. I need a solution for the bug when you can assign a value to a property without setter and
proper expression of this in the interfaces.

talk, talk , talk ....

TypeScript already generates defineProperty for ES5 getters/setters.
And, as I have shown, in this case we can declare properties for prototype, that is also important.

I'm going to take a look at implementing something here. My current thoughts are:

  • Introduce a readonly modifier that can be applied to property and method declarations in interfaces and classes, and infer a read-only property when a class includes only a get accessor declaration for a particular property.
  • Allow the readonly modifier to also be applied to interface and class declarations, making all members introduced in that interface or class declaration read-only.
  • Disallow assignments to read-only properties, except for property initializers in classes and property assignments in constructors.
  • In subtyping and assignment compatibility checks, allow a read-write property to be treated as a read-only property, but not vice versa.

This effectively amounts to "shallow" immutability. With that in place, _deeply immutable_ object types can be created by manually ensuring that all properties are read-only properties of primitive types (string, number, or boolean) or object types that are themselves deeply immutable (transitively).

I think this would cover the type checking side of things. I'm not sure we really want this to affect code generation (e.g. by using Object.defineProperty) since there is overhead associated with that.

This effectively amounts to "shallow" immutability.

Isn't that shallow immutability logic the same logic as const, therefore relatively straight forward to guard against?

Could readonly also be applied to parameter types as a type modifier providing a read-only reference to an existing object? Only getters and readonly members would then be available to that function. e.g.:

function doThis( someObject: readonly Foo ) {
    someObject.prop = 123; // error
    var v = someObject.prop; // ok
} 

In a similar way to C++ const. Without a type-modifier I suspect we'll end up writing interface definitions twice, once for immutability and again for mutability.

@kitsonk The difference between const and readonly is that it isn't possible to alias a const and therefore you're guaranteed it will never be mutated. With readonly you can fairly easily create a type with all read-only properties that aliases an object with read-write properties. In that scenario it is still _possible_ for mutations to happen, it's just that _you_ can't cause them.

@joewood The issue with readonly as a type modifier is that it may not do what you expect. For example, a readonly string[] would still have a push method that mutates the array. All readonly would do is keep you from assigning to the properties of the array.

@ahejlsberg maybe I not following, but wouldn't push be inaccessible because it wouldn't be marked as readonly because it mutates. Whereas functions like slice would have a readonly modifier, or doesn't it work like that.

It's probably worth breaking down several of the concepts of readonlyness that some languages have so we can clarify which would and would not exist:

  1. Is the object I'm referencing _mutable_ or _immutable_ ? i.e. am I allowed to do things to this object which mutate its state, via this reference?
  2. Is this reference (primitive or otherwise) a _mutable_ reference or an _immutable_ reference? i.e. am I allowed to change this reference to point to a different object (or different value)?
  3. Is a given property of an object ever settable, even when the object is mutable?

Using C++ as the most direct analogy, we can understand the difference between 1 and 2 as the difference between const int* x and int* const x. Since C++ doesn't have property getters/setters, point 3 reduces to point 1 (modulo the mutable keyword).

Since C++ doesn't have the ability to set e.g. std::vector.insert on an arbitrary object reference, it doesn't have to make any subtle distinctions that a hypothetical JavaScript-based full understand of mutability would require. To clarify: there are two concepts you might want to express about Array#indexOf or Array#push -- you don't want to allow arbitrary _setting_ of those properties, but only the latter causes mutation of the underlying object when invoked. It's worth thinking about how std::vector.size() is a _method_, rather than a field, in that context (you can't set the size of a mutable std::vector via that member).

What's on the table (as far as I understand) is the following:

  • You _can_ declare that object properties are not settable. For example, setting the length of a Function does nothing and should be a compile-time error.
  • You _cannot_ declare or determine that a method does or does not alter the state of an object. For example, calling someArray.slice() (which does not modify the object) and someArray.reverse() (which does) are indistinguishable.
  • You _can_ declare that all fields of a class or interface are read-only, but this is just a shortcut for declaring each property of that type with the readonly modifier.
  • Existing behavior of const vs. let/var cover reference immutability for variables

Since now I've written all this and now realize that there's a much simpler explanation, consider this symmetry:

const items: string[] = []; // OK, initializer
items.push('ok'); // OK
items = []; // Nope, const reference

and

readonly class MyClass {
  items: string[]; // readonly because containing class is readonly
  constructor() {
    this.items = []; // OK, initializer
  }
}
let m1 = new MyClass();
m1.items.push('hello'); // OK
m1.items = []; // Nope, readonly property
m1 = new MyClass(); // OK, not a const reference

const m2 = new MyClass();
m2.items = []; // Still an error
m2.items.push('world'); // Still OK
m2 = new MyClass(); // Nope, const reference

...and if the user wanted more run-time immutability they could:

readonly class MyClass {
  items: string[]; // readonly because containing class is readonly
  constructor() {
    this.items = []; // OK, initializer
    Object.freeze(this.items);
  }
}

const m3 = new MyClass();
m3.items.push('world'); // Throws an error in strict mode (or noop otherwise)

TypeScript hasn't "tracked" that type of immutability to date and I suggest the proposal for readonly also would just leave that behaviour to the runtime?

@ahejlsberg
Maybe your readonly modifier is not the same as const/final modifier in classes, that me and some other people expect.
I'm looking for ability of creating constants in classes such as in other languages and as we can see in javascript standard objects: Node.ELEMENT_NODE, XMLHttpRequest.DONE and other (in js we also can have access to these constants from instances, because of prototypes, but it's not really needed).
And these constants in js is read-only — I just want to have ability to declare such constants in TypeScript easely.

But, maybe, your readonly is OK, I have only one question — why you don't want to make class instance read-only properties assigned once for prototype and not for each instance?

Such a long discussion and finally things become clearer!

@RyanCavanaugh All that's on the table is correct but there's a small thing I want to mention about - the difference in getter/setter accessors visibility.

I agree that an object property can have no setter:

const foo = {
    get bar() {
        return 0;
    }
}

foo.bar = 1; //error

I think this is clear. But here's a bit more complex example that describes the problem:

declare abstract class Emitter {
    new (): Emitter;
    on: (name:string, handler: (arg:any) => void) => void;
    off: (name:string, handler: (arg:any) => void) => void;
    protected emit: (name:string, arg:any) => void;
}

class Person extends Emitter {
    constructor(name:string) {
        super();
        this.name = name;
    }

    private _name:string;
    get name() {
        return this._name;
    }
    set name(value) {
        if (this._name !== value) {
            this._name = value;
            this.emit('change:name', value);
            //this way is better
            this.updatedAt = new Date();
            //than this way
            this.setUpdatedAt(new Date());
        }
    }

    ////
    private _updatedAt:Date;
    get updatedAt() {
        return this._updatedAt;
    }
    private set updatedAt(value) { //Getter and setter do not agree in visibility
                //some logic and a simple readonly (our absence of a setter) is not enough
        if (this._updatedAt !== value) {
            this._updatedAt = value;
            this.emit('change:updatedAt', value);
        }
    }

    //// method implementation - but what's the point in it?
    private setUpdatedAt(value) {
        if (this._updatedAt !== value) {
            this._updatedAt = value;
            this.emit('change:updatedAt', value);
        }
    }
}

const entity = new Person('Mike');
entity.on('change:updatedAt', console.log.bind(console));
entity.name = 'Thomas';
//but manually setting updatedAt should be forbidden
entity.updatedAt = new Date(); //restricted

Here, property updatedAt actually can have a setter but it should not be accessable outside of Person. Moreover this setter contains complex logic and a simple readonly or an absence of a setter is not enough.

I agree that private setter is just a syntax sugar for private methods with additional logic (like emitting) but I think it is inconsistent when part of logic related to a field is in a property (getter) and another in a method (actual private setter).

This way getters/setter are implemented in C#, I think it would be usefull to allow different visibility at a compile time, though this contracts will be lost in runtime.

UPDATE I've found the duplication of my suggestion: #2845 but it is linked and closed

@RyanCavanaugh yes, understood. My two requests are really:

Type Modifier
Not having a type modifier on a reference (no. 2 above on your list) will mean that if you want to pass around an immutable reference to an object you will end up having to duplicate the interface definition with a simple readonly before the declaration. That seems a little frustrating, especially given how popular immutability is becoming on the client side. So something like this (even though it's only shallow)

interface Foo {
   x: number;
   y: Bar;
}

function doSomething( foo: readonly Foo ) { // or maybe using the const keyword
   foo.x = 10; // error
   var x = foo.x; // ok
   foo.y.bar = 10; // unfortunately ok because only shallow
}

Note, without this the developer would need to declare Foo and also a duplicate interface ImmutableFoo.

Context Type Modifier
Second request is to be able to decorate a function that acts as a type modifier for this. Property getters have this set implicitly.

interface Foo {
    readonly getState() : number;
    resetState();
}

function readFoo( foo: readonly Foo ) {
    let state = foo.getState(); // OK, works as readonly context function
    resetState(); // error - mutating function
}

With a type modifier you _could_ define a getter that returns a readonly type, so the immutability isn't just shallow. I think the interface definition would need to be extended to support that, or add some sort of immutable directive to the type definition.

@Avol-V
Both Node.ELEMENT_NODE, XMLHttpRequest.DONE are not constants but enums which are immutable be design and you can just export them from the module along with the class using them - this is the proper way in ES6/TS.

If you need an immutable property on the class you can define it with only a getter:

export enum NodeType {
    ELEMENT_NODE,
    SOME_OTHER_NODE_TYPE
}

export class SomeElement {
    private _type:NodeType;
    get type() {
        return this._type;
    }
}

This works right now except of the ability to write to a property without a setter which was considered a bug eariler in the conversation. No readonly/final/immutable etc.

@raveclassic
No, Node and XMLHttpRequest and others — is a classes, not enums. And ELEMENT_NODE and others — is constant in namespace of this class. Your example is wrong.
Correct example is:

class Node
{
    …
}
namespace Node
{
    export const ELEMENT_NODE: number = 3;
}

But in this case I have troubles with inheritance.

@Avol-V Yes, you are right, misunderstood. I can see now that your point is about inheriting class constants.
The problem here is that this issue is bloated and should be split. We are all talking about different things.

@Avol-V The readonly modifier would be applicable on both instance and static members. The following two constructs would effectively have the same meaning:

class Foo {
    static readonly ONE = 1;
}
class Foo {
}
namespace Foo {
    export const ONE = 1;
}

The readonly modifier would have no other effects than disallowing assignments to the property and affecting assignability of the containing type (i.e. a read-write property can become read-only, but not vice versa). There are numerous other meanings it _could_ have (e.g. put the member on the prototype object instead of on instances, control read-only-ness of the result if the member is a method, attempt to enforce deeper immutability, etc.), but those are all orthogonal issues that aren't addressed by what I'm proposing. As the length of this discussion thread aptly demonstrates, no _single_ modifier can do it all.

@ahejlsberg @RyanCavanaugh Thanks so much for digging into this a bit!

My personal preference is shallow immutability based on the following:

For classes: Inferred by a property getter that is not accompanied by a setter. (This is a guaranteed runtime failure. If we know that, it seems aligned with the design goals of the language.)

For interfaces: either get/set keywords or a readonly/const keyword that can be negated in an extending interface by a non-readonly/const declaration of the same member.

Many of the issues that have been closed and linked to this issue are asking for more or less the equivalent pattern in C#. Also, it is my impression that deep immutability is not aligned with the ES roadmap.

@joelday

Also, it is my impression that deep immutability is not aligned with the ES roadmap.

I've had the same impression for a while. And for what it's worth, I've been finding that the direction JS has been going has been making me question how functional it really is. I'm getting the feeling it's functional in the sense Common Lisp or Scheme is functional, in that it has most of the basics like lambdas, lexical closures, and TCO (last two as of ES6), but it's assumed that if you need anything truly functional like immutable structures or records, you'll use a library. And mutability is generally the default. There are no typeclasses, sum types, or pattern matching conditionals (without Sweet.js, and no, this doesn't count) in the language, and such requests for TypeScript haven't really gotten very far.

I have tried to provide a couple decent ideas within es-discuss myself, and although they seemed to be the best received out of all the proposals people tried, mine still had implementors concerned of _performance_ for one of them and the other would be very complex to implement, since JavaScript is mutable by default, and engines are optimized for that (it takes many new heuristics to make it fast again - see Erlang for a functional, dynamic language, and even that took a _lot_ of work).

@raveclassic

If you need an immutable property on the class you can define it with only a getter

Edit: this does work now.

I would prefer property syntax similar to c#.

public foo : string { get; } meaning that it is readonly.

Support for readonly properties and index signatures (#6532) is now in master.

This is awesome, thank you!

Great! I'm glad to see this has finally been resolved.

Thanks a lot for this! Awesome!

Thanks! That's a tricky one to handle elegantly. Looking forward to using it.

Thanks very much!

Thanks guys! Looking forward to TypeScript 2.0!

excellent!

An important missing side note: https://github.com/Microsoft/TypeScript/issues/13003#issuecomment-267787964

readonly prevents direct mutation of the declared property through that reference. That's it.

In plain English it means that #13002 is a use case yet to be looked at

I didn't find anywhere best practices for readonly instead of get-only properties.
Should I change all get properties to readonly ones?
The main difference that I see is that readonly property will become a regular read-write property in JS whereas get-only property is not.

@NN--- Generally the best practices are: private readonly members and public and protected get-only properties on classes. Use readonly for interfaces that will be implemented by classes that have get-only properties. Can add examples later. OTOH maybe StackOverflow, especially their new docs site is a better venue.

@joelday TypeScript misses shorthand for such get-only properties.
If only I could write 'public get prop: string;' instead of readonly + get.

A getter only property will be inferred as readonly automatically.

@mhegazy I understand this.
I meant the short syntax as in C# and other languages:

interface A {
    readonly x: number;
}

class B implements A {
    readonly _x: number;
    get x(): number {return this._x; }
}

class C implements A {
    get x(): number; // This should be equivalent to B.x implementation
}

And readonly x: number is not good enough?

Or u r looking for public get private set?

It is debatable and as I said I didn't find what is considered to be a good practice.
The main concern is that readonly becomes 'read-write' in the output JS and it can be changed by some bad code.
@joelday Suggests to use get-only properties in the class though.
@mhegazy Yes, but be more specific , constructor only set.

Terse get syntax like C# was discussed in #10911.

Since properties (at the moment) aren't ES spec, it could be argued though that the readonly modified in a class could make a non-writable value descriptor. If someone wanted to truly shadow the value, a getter only would be the other way. So this:

class A {
  readonly a = 'foo';
  get b() {
    return 'bar';
  }
}

Would de-sugar to:

var A = (function () {
    function A() {
    }
    Object.defineProperty(A.prototype, "a", {
        value: 'foo',
        writable: false,
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(A.prototype, "b", {
        get: function () {
            return 'bar';
        },
        enumerable: true,
        configurable: true
    });
    return A;
}());

Why is readonly diffrent from private? Privacy is not maintained in the generated js either.

@mhegazy You are correct. I mistakenly thought that private members are implemented in some different way.
Then it makes sense for same way readonly implementation.

I guess it is offtopic now. It needs some other syntax for truly readonly properties to be implemented by defineProperty and privates via symbol or some other nice hack.
Thanks.

The usage of readonly is not completely clear for me. I wrote a question on Stackoverflow.

This following code compiles, but is it semantically correct?

interface Counter {
  readonly name: string
  readonly value: number
  inc(): number
}

function counterFactory(name: string): Counter {
  let value = 0
  return {
    get name() {
      return name
    },
    get value() {
      return value
    },
    inc() {
      return ++value
    }
  }
}

The member value is declared as readonly but its value is changing.

I would like to know if this use of readonly on the member value is a good way to proceed. The syntax is OK. But is this example semantically correct in TypeScript? Is that what the modifier readonly is for?

@paleo readonly is similar to const, it doesn't define deep immutability, just some way to make less mistakes.

const x = {a:1};
x = {a:2}; // Error , cannot change x
x.a = 2; // Ok, const is not deep
let x: {readonly a:number;} = {a:1};
x = {a:2}; // x is mutable do whatever you want
x.a = 2; // Error, 'a' is readonly and doesn't allow reassignment.

For true immutability, you need to write code which makes types immutable.

@paleo readonly doesn't guarantee that a value won't change in time, it just doesn't let you write to it. That's why the word is readonly, not constant.

it just doesn't let you write to it

that's not true either: https://github.com/Microsoft/TypeScript/issues/13002

@aleksey-bykov Previous issue title was a bit closer then...

I quote @ahejlsberg from his PR:

A good way to think of it is:

  • readonly = cannot be modified by me
  • const = cannot be modified, period

In my question on StackOverflow, I wrote two use cases using readonly. The first case matches well to _"cannot be modified by me"_. But the second case, that I have reported above, would rather be a _"cannot be modified by you"_.

Is that what the modifier readonly is for? Is it valid from a semantic point of view?

Is that what the modifier readonly is for? Is it valid from a semantic point of view?

Like a lot of the TypeScript typing system, it is not enforceable at run-time, it is a type contract. const is a ECMAScript run-time concept which TypeScript can fully model the run-time behaviour. readonly correctly aligns to run-time behaviour when the property in question has a _getter_ only or is a value property that is not _writable_. At other times, it is a design time only construct to improve type safety (just as string or number are design time only constructs).

readonly correctly aligns to run-time behaviour when the property in question has a _getter_ only or is a value property that is not _writable_.

Thank you, it's clearer.

I realize that I already had the answer in 2016, from @RyanCavanaugh . At that time the ES5 getter solution was not available to me because my code had to remain ES3 compatible, so I left angry and didn't take the time to understand the information. He said:

You can already have a getter-only wrapper around a private variable, which achieves the same semantics at the cost of slightly increased class size.

This is the final answer: yes, the readonly modifier can be used with an ES5 getter to achieve this semantics.

Was this page helpful?
0 / 5 - 0 ratings