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

Different access modifier for getter and setter #2845

Closed
oskrabanek opened this issue Apr 21, 2015 · 46 comments
Closed

Different access modifier for getter and setter #2845

oskrabanek opened this issue Apr 21, 2015 · 46 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@oskrabanek
Copy link

Could it be possible to implement different access modifier for getter and setter? This way the setter could be for example private/protected and the getter public. In some cases this in really useful when the value shall be read-only.

Example:

class MyClass {
    private _myProp: any;
    private set myProp(value: any) {
        this._myProp = value;
    }
    public get myProp(): any {
        return this._myProp;
    }
}
@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Apr 21, 2015
@RyanCavanaugh
Copy link
Member

#12

@raveclassic
Copy link

As #12 is closed with #6532 now it seems that this issue is out of scope.
Do you now have any plans on implementing different access modifiers?
Because readonly is not enough to solve what's described here as a property might be actually writable inside a class.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus and removed Duplicate An existing issue was already created labels Jan 28, 2016
@RyanCavanaugh RyanCavanaugh reopened this Jan 28, 2016
@raveclassic
Copy link

I believe I should also move my example here from related issue:

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 (private setter).

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

@mhegazy mhegazy added Declined The issue was declined as something which matches the TypeScript vision and removed In Discussion Not yet reached consensus labels Jan 29, 2016
@mhegazy
Copy link
Contributor

mhegazy commented Jan 29, 2016

The recommendation here is to use a public readonly getter and a private/protected backing field.

Accessors are symetrical with properties in the type system. anything we do will need to be manifested in the type and expressible on properties. Adding new access modifiers to enable private_set/public_get would increase the complexity of the language and the learning curve, and the value gained from this would not match the added complexity.

@mhegazy mhegazy closed this as completed Jan 29, 2016
@mhegazy mhegazy added the Too Complex An issue which adding support for may be too complex for the value it adds label Jan 29, 2016
@0815fox
Copy link

0815fox commented Jun 22, 2016

Anyway adding my +1 for this, in case it is considered again in future...

@Roam-Cooper
Copy link

I'd still like to see this. C# has it, and it's incredibly useful in many circumstances, especially when you have flags that want to be read externally to the class, but only set internally with private/protected.

I think the suggestion that it isn't used all that often is a farce in that the pattern isn't used because it's not available to use.

@zommarin
Copy link

zommarin commented Sep 1, 2017

This is a good addition to the language: The amount of complexity that this would add (from the perspective of a typescript programmer) is small. The concept is easy to understand and the compiler should be able to provide good error messages hinting of why you cannot access getters or setters due to scoping.

@markurtz
Copy link

markurtz commented Sep 5, 2017

I agree with the above users, this is standard for most other languages. The argument against it does not hold weight and the implementation should be reconsidered.

@blteblte
Copy link

blteblte commented Sep 28, 2017

I actually was surprised you can not do this in TS... +1 to the issue.
There shouldn't bee any increased learning curve with this

private get x() { ... }
public set x(value) { ... }

Imo if you can read english it is self-explanatory what private(protected)/public mean here. Also if you are defining the accessors in a first place - you probably already know what they are for.

P.s. About the error: "Getter and setter accessors do not agree in visibility" - well: that's exactly what I want them to do

@ianfp
Copy link

ianfp commented Nov 17, 2017

Here are two use cases where this would be handy:

Backbone.js, to avoid ugly .get() and .set() calls:

class Whatever {
    public get rotation(): number {
        return this.get('rotation');
    }
    private set rotation(rotation: number) {
        this.set('rotation', rotation);
    }
}

Properties that subclasses can modify:

class ExtendMe {
    public get someProperty(): string {
        // some stuff
    }
    protected set someProperty(prop: string) {
        // some stuff
    }
}

@ZLima12
Copy link

ZLima12 commented Dec 3, 2017

I would definitely like to see this, as it has been bugging me since I started using TypeScript.

@marc-gj
Copy link

marc-gj commented Dec 22, 2017

I agree with the people requesting this feature. I just tried having a public get() method and was surprised to see that my set and get must agree on visibility.

@mdarefull
Copy link

I've been reading you guys claiming it is too complex. Remembering the "C++ way" why it is different from:

private _myAttribute: string;
get myAttribute(): string {...}
setMyAttribute(value: string) {...}

I can see a lot of use cases for this, that's why I'm here right now.
What if I want myAttribute to be publicly accessible but allow only to be modified inside its class?
What if I want to add some custom logic each time the attribute is modified?
It can be a business rule, it can be just a logging to understand why it has been assigned a specific value, together with a breakpoint condition for debugging purposes, etc...

Basically, we want a method to be used each time we modify the attribute together with the syntactic sugar that makes it seems we're just assigning a value, instead than calling a method.
C# has the concept of properties for this and I thought TS inherited that concept, but not allowing different accessibilities is a big limitations for people that think like me and make us fall back to the "C++ style".

@Giwayume
Copy link

"It's too hard" should never be a reason to not implement an incredibly useful feature.

@tempestwf
Copy link

Adding my +1. It seems very odd to me that in a language that is so completely in so many regards you can't do this.

@TomOeser
Copy link

TomOeser commented Oct 2, 2019

+1
Typescript would definitely benefit from this functionality!

@LevYas
Copy link

LevYas commented Oct 30, 2019

TypeScript is great even after C#, but this pointless limitation irritating me quite often. Please reopen it.

@mcpiroman
Copy link

would increase the complexity of the language

Is this really this complex comparing to things like readonly [P in keyof T]: T[P]?

@0xTomDaniel
Copy link

0xTomDaniel commented Feb 16, 2020

Bump. Everyone wants this feature. Shouldn't the TypesScript community get to decide?

@snarfblam
Copy link

would increase the complexity of the language

Is this really this complex comparing to things like readonly [P in keyof T]: T[P]?

The complexity comes into play with the interaction of other language features. Unfortunately I can't find it, but IIRC RyanCavanaugh gave an example where this feature could allow you to set up and then violate invariants via inheritance using a class expression. Just because it's easy to write a certain declaration, that doesn't mean it'll always be easy to reason about how it affects things.

The question is which problems will a given feature solve, and which will it create. The former is easy to answer, the latter can surprisingly hard to answer. Unfortunately the TS team sometimes seems to respond with "OMG complexity" instead of illustrating the problem. (To be fair, the more time they spend answering query x for the nth time, the less time they have to develop.)

I don't 100% agree with the notion of "shouldn't the community get to decide", because if there is consensus among the experts actually developing the language, that should tell you something. But I think with something as requested as this, a thoughtful explanation from the team on what the trade-off is and why they're against it isn't too much to ask for.

And personally, I think the trade-off is 1000% worth it in this case. But if I can't be bothered to spec it out and make it work, I suppose I don't have too much right to complain.

@yvbeek
Copy link

yvbeek commented Jun 17, 2020

Would this be a good time to revisit this issue?

The readonly modifier is great, but there are many cases where you want to be able to change the value inside of the class and still have everything be read-only on the outside.

I often choose not to make the property readonly because I don't like the noise that a private backing field + the properties add.

It would be great if there was some syntactic sugar to do this for you. Something like:

// Option 1: C# style
public name: string { get; private set; }

// Option 2: Swift style
private(set) name: string

// Option 3: Swift struct-style
public readonly name: string

mutating changeName(name: string) {
  this.name = name
}

// Option 4: New keyword
public frozen name1: string
public readonly name2: string

I like option 2, imo it would fit well into the TypeScript language.

With option 3, you can only change readonly fields in functions that are marked as mutating

With option 4, frozen can only be set in the constructor, readonly can be set inside of this class, not by any external classes or classes inheriting from this class.

@trusktr
Copy link

trusktr commented Aug 4, 2020

For reference, @yvbeek's ideas on more flexible modifiers is better suited for the discussion over in #37487.

This issues is specifically for getters and setters, and has a super high number of upvotes! I think it would be useful to give getters and setters differing access modifiers (and we can update the existing set of modifiers over in #37487 if the TypeScript team ever decides it to be an acceptable thing to move forward with)

@0xTomDaniel
Copy link

0xTomDaniel commented Aug 5, 2020

I don't 100% agree with the notion of "shouldn't the community get to decide", because if there is consensus among the experts actually developing the language, that should tell you something. But I think with something as requested as this, a thoughtful explanation from the team on what the trade-off is and why they're against it isn't too much to ask for.

@snarfblam Not to clog this thread with an irrelevant comment, but I think you have revealed a core principle for the way government should operate.

@CatalinMustata
Copy link

Not having this feature is a real pain for me and (among other things) made me switch from TS/NodeJS to something more... type safe. It's all fine and dandy, but when you're working on a complex project with a a lot of data structures (deeply nested many times) and you cannot model the data properly, you get the feeling that this is not a language "for big boys".

In my particular case, I want the property to be readonly, but modifiable from the inside... and also serialized to JSON. Too many hoops to jump through.

@yvbeek
Copy link

yvbeek commented Aug 21, 2020

This feature might follow the same path as optional chaining. People will ask for this feature for years and then finally it will be added to the language, because it is practical and other languages offer the same feature.

Otherwise I hope that some implementation will become part of EcmaScript and then make its way to TypeScript.

@Dazco
Copy link

Dazco commented Sep 12, 2020

I recently just switched to typescript and I've been loving the language. I'm really bummed out that this practical feature that exists in other languages hasn't been implemented here. Please do reconsider adding it in a future release regardless of how much complexity you think it might add to the language.

@ScreamZ
Copy link

ScreamZ commented Sep 23, 2020

I achieved something similar to c# with getters that way:

export class I18nService {
  private static ref: I18nService;

  public static get instance(): I18nService {
    if (!I18nService.ref) {
      I18nService.ref = new I18nService();
    }

    return I18nService.ref;
  }
}

@trusktr
Copy link

trusktr commented Nov 18, 2020

Type errors like the following are easy to understand and are not complicated:

Property 'foo' is writable only in protected scope within class 'Blah' and its subclasses.

or

Property 'foo' is readable only in protected scope within class 'Blah' and its subclasses.

etc, and similar with private.

That's honestly not complicated.

@danielrentz
Copy link

danielrentz commented Dec 4, 2020

BTW, I stumbled over this problem too, and I am using this kind of "hack" in the meantime:

// somewhere.ts
declare global {
    type Writable<T> = { -readonly [P in keyof T]: T[P]; }
}

// example.ts
class Example {

    public readonly prop: number;

    public doSomething(n: number): void {
        (this as Writable<this>).prop = n;
    }
}

Technically, this could be used everywhere, but using this workaround should be restricted to code inside class methods.

@snarfblam
Copy link

BTW, I stumbled over this problem too, and I am using this kind of "hack" in the meantime:

I've played with this idea myself, but the problem is you have no way to distinguish between "public read-only" and truly read-only properties. This makes it not very suitable as a general-purpose solution.

@RyanCavanaugh RyanCavanaugh reopened this Feb 3, 2021
@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus and removed Declined The issue was declined as something which matches the TypeScript vision Too Complex An issue which adding support for may be too complex for the value it adds labels Feb 3, 2021
RyanCavanaugh added a commit to RyanCavanaugh/TypeScript that referenced this issue Mar 24, 2021
@RyanCavanaugh
Copy link
Member

There's a Playground build for PR 42425 available for trying out and I'd appreciate any feedback on how it meets folks' needs here. Thanks! See comments in #42425 on how this works.

@snarfblam
Copy link

This is nice. I hope it's a step toward asymmetric accessibility of data properties.

@legistek
Copy link

This is great to see. Apologies my prior comment was directed to the disnissive way this issue was summarily closed by the former team manager years ago. @RyanCavanaugh I'm glad to see typescript gaining more parity with languages like C#. Great work.

@codearmadillo
Copy link

The recommendation here is to use a public readonly getter and a private/protected backing field.

Accessors are symetrical with properties in the type system. anything we do will need to be manifested in the type and expressible on properties. Adding new access modifiers to enable private_set/public_get would increase the complexity of the language and the learning curve, and the value gained from this would not match the added complexity.

I am sorry but this is a rubbish reason. Typescript includes features like private constructors, yet you say that the complexity of different getters/setters is too high and steepens the learning curve. Like somebody mentioned here, it is hard to make should not be an argument against a standard in most modern programming languages. Your suggestion to use private/protected is not really helpful either - it makes the code less readable and it adds complexity to it.

@RiftLurker
Copy link

I am sorry but this is a rubbish reason. Typescript includes features like private constructors, yet you say that the complexity of different getters/setters is too high and steepens the learning curve. Like somebody mentioned here, it is hard to make should not be an argument against a standard in most modern programming languages. Your suggestion to use private/protected is not really helpful either - it makes the code less readable and it adds complexity to it.

You might want to consider reading more than the first few comments. The one you quoted was written over 5 years ago, several months before even the addition of private constructors (TypeScript 2.0) you're talking about.
TypeScript has gone a long way since then, see the comments and linked commit (which landed in 4.3) right above.

@codearmadillo
Copy link

@RiftLurker I know that, but that does not change the point. I did read the thread, and I do know the changes are in 4.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests