TypeScript should have "Records" like C#7

Created on 14 Sep 2016  ·  11Comments  ·  Source: microsoft/TypeScript

Proposal

Typescript should be able to generate immutable objects with accessor properties and value semantics, similar to the "records" proposal for C# 7.0

Rationale

Currently I have to write a lot of boilerplate code to create an object with accessor properties and value semantics. I think this can easily be streamlined by the TypeScript compiler.

Example of what I have to write currently

class Person {
    private _firstName: string;
    private _lastName: string;

    constructor(firstName: string = null, lastName: string = null) {
        this._firstName = firstName;
        this._lastName = lastName;
    }

    public get firstName(): string {
        return this._firstName;
    }

    public get lastName(): string {
        return this._lastName;
    }

    public equals(other: Person): boolean {
        return other !== void 0
            && other !== null
            && this._firstName === other._firstName
            && this._lastName === other._lastName;
    }
}

What I would like to write in future

class Person {
    constructor(
        public get firstName: string = null,
        public get lastName: string = null) {       
    }
}

Note the use of get in the constructor

Either of the examples above should produce this (the top example did produce this)

var Person = (function () {
    function Person(firstName, lastName) {
        if (firstName === void 0) { firstName = null; }
        if (lastName === void 0) { lastName = null; }
        this._firstName = firstName;
        this._lastName = lastName;
    }
    Object.defineProperty(Person.prototype, "firstName", {
        get: function () {
            return this._firstName;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Person.prototype, "lastName", {
        get: function () {
            return this._lastName;
        },
        enumerable: true,
        configurable: true
    });
    Person.prototype.equals = function (other) {
        return other !== void 0
            && other !== null
            && this._firstName === other._firstName
            && this._lastName === other._lastName;
    };
    return Person;
}());

Duplicate Suggestion

Most helpful comment

Perhaps also worth noting that if all you need is a simple record, classes are overkill.

const p1 = { firstName: "Bart", lastName: "Simpson };

And we're done. If you want to centrally enforce the type that a person conforms to, e.g. to stop writing to the properties:

interface Person {
    readonly firstName: string;
    readonly lastName: string;
}

Then assign your object literals to that. To get freezing, write a simple constructor-like function:

function person(firstName: string, lastName: string): Person {
    return Object.freeze({ firstName, lastName });
}

const p = person("Homer", "Simpson");

p.firstName = "Marge"; // stopped at compile-type
(p as any).firstName = "Marge"; // stopped at runtime

All 11 comments

The following is succinct and does most of what you are asking for:

class Person {
    constructor(
        public readonly firstName: string = null,
        public readonly lastName: string = null) {       
        Object.freeze(this); // this adds runtime immutability as well
    }
}

Since equals is no sort of standard method in JavaScript, I can't imagine that part being implemented by the TS compiler.

@yortus

  • In which version is "readonly" implemented?
  • Does this emit immutable fields or ES5 accessor properties?

Emiting ES5 accessor properties with only a getter would naturally create immutable properties...swings and roundabouts as to whether emitting getters or using Object.freeze would be better.

_In general I just thing the whole property syntax in TypeScript sucks._

readonly is a 2.0 feature. Read about it in #6532. It's purely for static type-checking and doesn't affect the emit, so the JavaScript output contains ordinary mutable fields.

I do agree with @yortus, the intended scenarios should be covered by readonly properties.

Emiting ES5 accessor properties with only a getter would naturally create immutable properties.

that too is the case in TS 2.0

In general I just thing the whole property syntax in TypeScript sucks.

it is really JS syntax/semantics.

Perhaps also worth noting that if all you need is a simple record, classes are overkill.

const p1 = { firstName: "Bart", lastName: "Simpson };

And we're done. If you want to centrally enforce the type that a person conforms to, e.g. to stop writing to the properties:

interface Person {
    readonly firstName: string;
    readonly lastName: string;
}

Then assign your object literals to that. To get freezing, write a simple constructor-like function:

function person(firstName: string, lastName: string): Person {
    return Object.freeze({ firstName, lastName });
}

const p = person("Homer", "Simpson");

p.firstName = "Marge"; // stopped at compile-type
(p as any).firstName = "Marge"; // stopped at runtime

The following is succinct and does most of what you are asking for:

class Person {
    constructor(
        public readonly firstName: string = null,
        public readonly lastName: string = null) {       
        Object.freeze(this); // this adds runtime immutability as well
    }
}

@yortus that is excellent.
I would like to add that it can be made even more concise and idiomatic

class Person {
    constructor(
        readonly firstName?: string,
        readonly lastName?: string) {
        Object.freeze(this); // this adds runtime immutability as well
    }
}

Also for record equality you can use this function : https://www.npmjs.com/package/deep-equal when feeling lazy (like I've done a few times e.g in alm) :rose:

@series0ne 'readonly' emits read-write property in JS.
It is not the same as getter-only property.
Unfortunately there is still no shorthand for getter-only property.

@NN Yes I know. readonly is rather useful (provided one does not play with the emitted JS). I also find it quite useful to use readonly in conjuction with Object.freeze(...)

For example:

class Foo {
    constructor(readonly bar: any) {
        Object.freeze(this);
    }
}

Now it's really readonly!

Actually, readonly can be useful for creating immutable classes if you

return Object.freeze(this);

from the constructor. The advantage over using accessors is that you can leverage inline initialization, parameter properties, and the resulting object is spreadable.

All in all though, I much prefer factories to classes. Especially with the addition of Object Rest/Spread, object literals are better than ever.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

blendsdk picture blendsdk  ·  3Comments

jbondc picture jbondc  ·  3Comments

wmaurer picture wmaurer  ·  3Comments

siddjain picture siddjain  ·  3Comments

fwanicka picture fwanicka  ·  3Comments