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

Properties with only getter or setter in an interface #814

Closed
IKoshelev opened this issue Oct 3, 2014 · 16 comments
Closed

Properties with only getter or setter in an interface #814

IKoshelev opened this issue Oct 3, 2014 · 16 comments
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript

Comments

@IKoshelev
Copy link

Right now interfaces use the same syntax for properties with and without accessors. This is fine for duck typed interfaces, but the problem is, there is no way to specify properties with only one part (getter or setter) present. Usually, they are read only, without setter. The problem is, we can't tell this to a compiler, so it will not warn us when assigning to such a prop and JS runtime will simply ignore this assignment without throwing.

It would be good to have syntax like this

interface IEmployee{
     get fullName: string;
     set firstName: string;
     set lastName: string; 
}

to prevent such errors.

@RyanCavanaugh
Copy link
Member

See #12

@RyanCavanaugh RyanCavanaugh added Duplicate An existing issue was already created Suggestion An idea for TypeScript labels Oct 3, 2014
@IKoshelev
Copy link
Author

#12 is a bad example - it deals with just a part of the problem, as, while 'getter only' is a more common case, setter only is also possible. And Immutability is not concerned here - since we are talking getters, another function in the object may well modify the value returned by our getter (the whole concept of Immutability is more about state, and accessors might not have state at all). We don't need a new keyword for the good old 'get' either. What is needed is a way to tell the compiler 'only get for property X is valid' or 'only set to property X is valid', analogous to what we can already do in classes implementing those interfaces.

@RyanCavanaugh
Copy link
Member

The readonly keyword referenced in #12 does not deal with immutability of reference-type values. For example, in interface X { readonly n: string[]; }, x.n.push('foo'); would definitely still be legal.

We're definitely not bothering with write-only properties. This is not common enough to justify complicating the type system with it.

If you could write some examples of how you think interface T { readonly x: number; } is different from interface T { get x: number; } we might be able to clarify more.

@IKoshelev
Copy link
Author

I'm not talking about reference type values, I'm saying that the whole concept of Immutability doesn't have much to do with a function, which a getter is. And if you look at it from a more duck typing point of view (how it can be used, not what it should be), consider the fullName example. I expect it to be get only, but not immutable at all, just mutated with other setters. That is why I don't think it is a good idea to mix discussion about getters / setters on an interface and immutability, or at least it should not be limited to that.

As for write only properties - I don't think there is even a choice here, other then to make them possible in interfaces, unless you are willing to make them impossible in actual classes. And that, I think, would be a bad idea, considering they are present in ECMA 6. As long as JS supports them, so should you, imho.

The final point - how is 'readonly' different from 'get', well, my train of thought is as follows. Should I see 'readonly' in TS code without prior knowledge, I would think like this - we already have 'get', so 'readonly' must mean something else (behaves slightly different in some way), because there is no point in having two names for the same concept. And that would lead to confusion. Also, I expect to see same syntax for such property on an interface and on the class implementing it.

P.S. get is shorter :-)

@danquirk
Copy link
Member

danquirk commented Oct 3, 2014

The syntax is largely irrelevant. Whether or not the keyword is get or readonly is not important if they represent the same concept which from your description they appear to do. You want a way to specify that a fullName can only be get/read, an attempt to set/write it will fail silently because no one implemented a setter for it. This is precisely the problem #12 is exploring, namely, what are the ramifications of allowing such a type as it relates to assignment compatibility with other types, property access rules, etc.

@IKoshelev
Copy link
Author

The syntax is relevant at least because where there is a 'get' keyword - there should be a 'set' keyword, and #12 doesn't mention it in any form. Also, I wouldn't call calling things 'unimportant'.

As for the example in #12 , since then there have been 3 or 4 requests for this feature and the authors either didn't find the original issue or initially considered it relevant to theirs (myself included). That is a sign that the original discussion is not what they are looking for.

Btw, the case with point pt4 can illustrate why 'readonly' is bad. I COULD easily expect it to be immutable, not just by me, but anyone, and be puzzled why it changes between callbacks (by another function that had pt3 passed to it). I would never make that assumption about a 'get'. As I said earlier, I expect 'reasonly' to be associated with state, not a function, and constant state at that. As in 'nothing can change this by the time your code receives it'. Get is much less assumptive, basically, it conveys nothing beyond "you can get a value of this property"'

@danquirk
Copy link
Member

danquirk commented Oct 4, 2014

There are no technical hurdles required to choose the syntax for this feature. The specific syntax used is not a factor in why this feature isn't yet implemented. While you may infer different things about the intended meaning depending on which word is chosen for the syntax the final semantics do not care about the chosen word, they simply must work.

I would never make that assumption about a 'get'.

Why?

// #1 A read only property
interface Employee {
    get name: string; // no setter exists, trying to assign a value to name will fail
}

// #2 A readable and writeable property with explicit get and set
interface Employee {
   get name: string;
   set name: string;
}

// #3 A readable and writeable property today
interface Employee {
    name: string;
}

The second form is redundant with the syntax that already exists. So the interesting case is the first, where there is only a getter and no setter. What does this mean? If there is no way to set a value, then the field is read only and we are trying to model an object with a name that can be read but cannot be written to. Otherwise if setting were safe we would've used the 2nd or 3rd form. Whether you choose to use the word get or readonly doesn't change the design you are trying to model and that design is the one with outstanding design questions in #12. If you have answers to the questions in that issue then please do post your thoughts in there.

@justinfagnani
Copy link

This issue doesn't seem like a duplicate of #12, as declaring that a property can be read, and that it can't be set are two very different things.

When I recently tried to write an interface like this:

interface HasTemplate {
  get template(): HTMLTempalteElement;
}

I wasn't trying to declare that template is readonly, only that it can be read.

This could be obviously implemented by a getter:

class A implements HasTemplate {
  private _template : HTMLTemplateElement;
  get template() : HTMLTemplateElement { return _ template }
}

but also by a field:

class B implements HasTemplate {
  template : HTMLTemplateElement;
}

readonly seems very different, in that it says explicitly that there is no setter. Though getters/setters in interfaces would play nice with readonly:

class C implements HasTemplate {
  readonly template : HTMLTemplateElement;
}

Given that JavaScript has getters and setters, it seems like interfaces that describe JavaScript objects should include them. @RyanCavanaugh Any chance this suggestion could be reopened?

@RyanCavanaugh
Copy link
Member

I don't understand what the distinction is. What's an observable difference between a property with a getter but no setter and a readonly property ?

@justinfagnani
Copy link

The difference is between describing having at_least a getter, and describing not having a setter.

Here's a table of interface declarations (columns) vs implementation (rows), and whether the implementation satisfies the interface (Note, I'm presuming that a readonly field in an interface is not satisfied by a read/write field in a class, I need to go check on that).

field getter setter readonly
field x
getter x x ?
setter x x x
readonly x x

The problem with readonly would be if it's not satisfied by a field. When I put a getter in an interface I want to allow it be satisfied by fields or getters.

@RyanCavanaugh
Copy link
Member

A read/write property (whether implemented as a field or a get/set pair) is substitutable for a read-only property.

@justinfagnani
Copy link

@RyanCavanaugh that's... weird. If that's the case, then why was new syntax invented rather than just allowing standard accessors in interfaces? And why is the name of the annotation readonly when it seems to mean readable?

Anyway, I guess my main questions were answered. Thanks.

@RyanCavanaugh
Copy link
Member

that's... weird.

If a read/write property weren't substitutable for a readonly property, the language would be literally unusable.

If that's the case, then why was new syntax invented rather than just allowing standard accessors in interfaces?

So that you can write this:

class X {
  readonly y: number;
}

And why is the name of the annotation readonly when it seems to mean readable?

I don't see why that name would be preferable.

interface X {
  readable a;
  b;
}
let y: X;
let z = y.b; // must be illegal since 'b' wasn't marked readable
y.a = 1; // might be legal, 'readable' doesn't imply 'not writeable'

@justinfagnani
Copy link

If a read/write property weren't substitutable for a readonly property, the language would be literally unusable.

I'm more referring to the fact that readonly does not actually mean "read only", it means that a property is at least readable, without saying anything about being writable.

So that you can write this:

JavaScript already has syntax for this though, it's:

class X {
  get y() { ... }
}

Since I prefer my TypeScript to be as small of a difference from JavaScript as possible (TypeScript team's opinion may differ, of course), and that interfaces are classes without bodies/initializers, I would think that this would be preferable over an entirely new keyword:

interface X {
  get y();
}

edit: I'm just bike shedding now... we don't have to spend time on this, unless you want to :)

@jake-knerr
Copy link

jake-knerr commented May 27, 2016

I don't understand what the distinction is. What's an observable difference between a property with a getter but no setter and a readonly property ?

A getter can return an evaluated expression.

class X {
  readonly y: number;
}

vs.

class X {
  get y (): number {
    // return an evaluated expression
    return 10 * 2 + myVar.jingle - 10; // etc.
  };
}

Of course you could move get y() into a function on the prototype, like getY,() but I am writing code to conform to a particular 3rd party API, so I need to use property accessors.

+1 for the OP request.

interface IEmployee{
     get fullName: string;
     set firstName: string;
     set lastName: string; 
}

@justinfagnani
Copy link

I hit this again when trying to write a declaration for a type that uses accessors, because I forgot that TypeScript doesn't support accessors in interfaces. I still this it's a usability/ergonomics issue, because the most obvious derivation of standard JavaScript syntax into TypeScript interfaces isn't used, but I realize now it's also a correctness issue.

To reiterate the usability issue, when writing a declaration for this class:

class X {
  get y() { ... }
}

I would expect to be able to just remove any method bodies and write:

declare module 'foo' {
  class X {
    get y();
  }
}

This is the most obvious thing to try, yet it doesn't work.

As for correctness, there is a difference in the shape of objects between a property and an accessor:

class WithProperty {
  x;
  constructor(x) { this.x = x; }
}
class WithAccessors {
  _x;
  constructor(x) { this.x = x; }
  get x() { return this._x; }
  set x(v) { this._x = v; }
}

let withProperty = new WithProperty(1);
withProperty.hasOwnProperty('x'); // true
withProperty.__proto__.hasOwnProperty('x'); // false

let withAccessors = new WithAccessors(1);
withAccessors.hasOwnProperty('x'); // false
withAccessors.__proto__.hasOwnProperty('x'); // true

So letting a class declaration describe it's accessors, which live on the prototype, separately from its fields/properties, which live on the instance would be the more correct solution.

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 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

5 participants