Skip to content
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

TypeScript should have "Records" like C#7 #10910

Closed
MrMatthewLayton opened this issue Sep 14, 2016 · 11 comments
Closed

TypeScript should have "Records" like C#7 #10910

MrMatthewLayton opened this issue Sep 14, 2016 · 11 comments
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript

Comments

@MrMatthewLayton
Copy link

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;
}());

@yortus
Copy link
Contributor

yortus commented Sep 14, 2016

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.

@MrMatthewLayton
Copy link
Author

@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.

@yortus
Copy link
Contributor

yortus commented Sep 14, 2016

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.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 14, 2016

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

@mhegazy mhegazy added Suggestion An idea for TypeScript Duplicate An existing issue was already created labels Sep 14, 2016
@mhegazy
Copy link
Contributor

mhegazy commented Sep 14, 2016

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.

@danielearwicker
Copy link

danielearwicker commented Sep 17, 2016

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

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Sep 18, 2016

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
    }
}

@basarat
Copy link
Contributor

basarat commented Sep 18, 2016

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) 🌹

@mhegazy mhegazy closed this as completed Sep 19, 2016
@NN---
Copy link

NN--- commented May 20, 2017

@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.

@MrMatthewLayton
Copy link
Author

@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!

@aluanhaddad
Copy link
Contributor

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.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants