Skip to content

Add Class type (i.e. a Record but no default values) #839

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

Closed
OliverJAsh opened this issue Apr 16, 2016 · 7 comments
Closed

Add Class type (i.e. a Record but no default values) #839

OliverJAsh opened this issue Apr 16, 2016 · 7 comments

Comments

@OliverJAsh
Copy link

OliverJAsh commented Apr 16, 2016

I want an immutable object with typed properties (TypeScript). I want something like a Record, but without default values.

In Scala we have case classes that do this. Would it be possible to add this type to immutable-js?

@myitcv
Copy link

myitcv commented Apr 16, 2016

@OliverJAsh is this in TypeScript?

Did this comment not work out for you?

@OliverJAsh
Copy link
Author

Yes, it is in TypeScript. Thanks for the link, I'm trying to make sense of it all but a lot of it is going over my head. Seems like it should be simpler?

@leebyron
Copy link
Collaborator

leebyron commented Apr 16, 2016

Unfortunately I don't think this is possible, as what you're asking for is syntactic rather than library code.

A default value must always exist, because you can always do myRecord.myField and it must return something, even if that something is undefined. For example, this is totally fine:

var MyRecordType = Record({
  myField1: undefined,
  myField2: undefined
})

@leebyron
Copy link
Collaborator

We have a slightly nicer syntax coming in a future version that lets you provide coercion functions rather than just default values:

44b20e4

@myitcv
Copy link

myitcv commented Apr 17, 2016

@OliverJAsh the immutable aspect you require does not require ImmutableJS. You can use the type checking of TypeScript to achieve the same thing (indeed there is a performance benefit to doing so).

Consider:

class A1 {
    private name: string;
    private age: number;
    private hobbies: Immutable.Set<string>;

    constructor() {
        // could set defaults here... or take parameters
        // to assign
    }

    Name(): string {
        return this.name;
    }

    SetName(v: string): A1 {
        if(v === this.name) {
            return this;
        }
        let res = this.dup();
        res.name = v;
        return res;
    }

    Age(): number {
        return this.age;
    }

    SetAge(v: number): A1 {
        if(v === this.age) {
            return this;
        }
        let res = this.dup();
        res.age = v;
        return res;
    }

    Hobbies(): Immutable.Set<string> {
        return this.hobbies;
    }

    SetHobbies(v: Immutable.Set<string>): A1 {
        if(v === this.hobbies) {
            return this;
        }
        let res = this.dup();
        res.hobbies = v;
        return res;
    }

    private dup(): A1 {
        let res = new A1();
        res.hobbies = this.hobbies;
        res.age = this.age;
        res.name = this.name;
        return res;
    }
}

let x1 = new A1();
let x2 = x1.SetName("Paul");

console.log(x1 === x2); // false
console.log(x1.Name()); // undefined
console.log(x2.Name()); // "Paul"

Here the model class A1 is "immutable" in a roughly equivalent sense to the API exposed by containers etc in ImmutableJS.

You will notice the primitive properties (string, number) can be typed as such because a string value is immutable. Array, Object etc are mutable, hence you must use an ImmutableJS container for the types of such properties (e.g. Hobbies).

If you are using the latest nightly version of TypeScript you can avoid methods for the getters and do the following:

class A1 {
    private name: string;
    private age: number;
    private hobbies: Immutable.Set<string>;

    constructor() {
        // could set defaults here... or take parameters
        // to assign
    }

    get Name(): string { // getter
        return this.name;
    }

    SetName(v: string): A1 { 
        if(v === this.name) {
            return this;
        }
        let res = this.dup();
        res.name = v;
        return res;
    }

    get Age(): number { // getter
        return this.age;
    }

    SetAge(v: number): A1 {
        if(v === this.age) {
            return this;
        }
        let res = this.dup();
        res.age = v;
        return res;
    }

    get Hobbies(): Immutable.Set<string> { // getter
        return this.hobbies;
    }

    SetHobbies(v: Immutable.Set<string>): A1 {
        if(v === this.hobbies) {
            return this;
        }
        let res = this.dup();
        res.hobbies = v;
        return res;
    }

    private dup(): A1 {
        let res = new A1();
        res.hobbies = this.hobbies;
        res.age = this.age;
        res.name = this.name;
        return res;
    }
}

let x1 = new A1();
let x2 = x1.SetName("Paul");

console.log(x1 === x2); // false
console.log(x1.Name); // undefined
console.log(x2.Name); // "Paul"

@coodoo
Copy link

coodoo commented Apr 18, 2016

@leebyron Is there any reason why 44b20e4 hasn't landed yet?

@cameron-martin
Copy link

cameron-martin commented May 17, 2016

A default value must always exist, because you can always do myRecord.myField and it must return something, even if that something is undefined.

The constructor could error if not given all the specified parameters. Then if you don't have a remove method, only set, then no default values are needed to fall back on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants