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

Suggestion: optional interface #371

Closed
vvakame opened this issue Aug 6, 2014 · 15 comments
Closed

Suggestion: optional interface #371

vvakame opened this issue Aug 6, 2014 · 15 comments
Labels
Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript

Comments

@vvakame
Copy link
Contributor

vvakame commented Aug 6, 2014

I think this is useful syntax :)

sample.

interface IFoo {
  foo: string; // not optional
}

class Bar implements IFoo? {
  // Bar does not have a foo property.
}

new Bar().foo;

How useful.

class Foo implements Lib.IListener? {
  constructor() { Lib.injectInto(this); }
}

module Lib {
  export interface IListener {
    on(data: any): void;
  }

  export function injectInto(target: any) {
    target.on = () => { /* do something... */ }; 
  }

  export function listen(receiver: IListener) {
    receiver.on("foobar");
  }
}

var obj = new Foo();
Lib.listen(obj);

Lib.listen({
  on: ()=> {} // on method required
});

In real world.

case 1.
https://github.com/borisyankov/DefinitelyTyped/blob/705102df2b652bdbadad23561675116a26e48760/space-pen/space-pen.d.ts#L52

case 2.
// Emissary#emitter
https://github.com/atom/emissary#emitter
// .d.ts
https://github.com/borisyankov/DefinitelyTyped/blob/b4aba562ef4192bc3f2e770ab2018f89e047836b/emissary/emissary.d.ts#L13
// usage in TypeScript (dirty hack!)
https://github.com/vvakame/language-review/blob/d23f8797bbb6e99d636399a14c7035c4809ab365/lib/util/emissary-helper.ts#L11

@basarat
Copy link
Contributor

basarat commented Aug 6, 2014

This is an alternative syntax suggestion for #340

But still 👍

@basarat
Copy link
Contributor

basarat commented Aug 6, 2014

Admittedly this way we do not break existing code.

@basarat
Copy link
Contributor

basarat commented Aug 6, 2014

Another use case is making mixins easier

@RyanCavanaugh
Copy link
Member

Major question I have here is that an important use case for mix-ins is that you would be able to add one of these optional interfaces after the fact, which doesn't work well with having to declare this on the original class declaration. A solution for this JS pattern should be able to handle both cases.

@vvakame
Copy link
Contributor Author

vvakame commented Aug 7, 2014

Sorry English is not my first language.
Do you say How to mix-in interface to already defined class??

I think we should expand TypeReference grammer.
TypeName [no LineTerminator here] TypeArgumentsopt to TypeName [no LineTerminator here] TypeArgumentsopt ImplementsClauseopt.

use case.

module Lib {
  export interface IListener {
    on(data: any): void;
  }

  export function injectInto<T>(target: T): T implements IListener {
    var modified = <T implements IListener?> target;
    modified.on = () => { /* do something... */ }; 
    return modified; // IListener? has compatiblity to IListener. I think this is programmer's duty.
  }

  export function listen(receiver: IListener) {
    receiver.on("foobar");
  }
}

var receiver = Lib.injectInto({ name: "vvakame" });
// typeof receiver === ({name: string;} implements IListener)
// I want to get expanded object type literal like {name: string; on(data: any): void;} on error message.
receiver.on("foobar");
receiver.name;
Lib.listen(receiver);

@s-panferov
Copy link

+1 to something that can fix the current TypeScript mixins issues. Things like ReactJS using mixins widely and now it is very hard to make TypeScript compiler check all this things property without some effort.

For example, there are more than 400 components in my project and it will be a little bit crazy to repeat React's method defs inside every component. On the other hand i want to type-check my code. So i find out to define classes like this:

class LinkComponent extends ReactComponentBase<LinkComponentOptions,{}> implements React.ReactComponentSpec<LinkComponentOptions,{}> {
   // extends Component<LinkComponentOptions,{}> for instance methods like setState
   // implements React.ReactComponentSpec<LinkComponentOptions,{}> for type check livetime methods
}

 // this is an `empty` class to satisfy the compiler
 export class ReactComponentBase<P, S> implements React.ReactComponent<P, S> {
    state: S;
    props: P;
    refs: { [ref: string]: React.ReactComponent<any, any>; };
    getDOMNode: () => Element;
    setProps: (nextProps: P, callback?: () => void) => void;
    replaceProps: (nextProps: P, callback?: () => void) => void;
    transferPropsTo: <C extends React.ReactComponent<P, any>>(target: C) => C;
    setState: (nextState: S, callback?: () => void) => void;
    replaceState: (nextState: S, callback?: () => void) => void;
    forceUpdate: (callback?: () => void) => void;
  }

This leads to emty class ReactComponentBase that includes to every component (in compiled js code) and looks very ugly. And for my project's react mixins i need to create additional (empty) classes that extends ReactComponentBase and provide correct mixin's methods.

export class MixinsComponent<P, S> extends ReactComponentBase<P, S> {
    // example of mixin method
    onValue: (bus: Bacon.Bus<any>, foo: (val: any) => void) => void;
}

I think that implementation of normal mixins and unit types will make TypeScript much more friendly to such kind of libs.

@sedwards2009
Copy link

Dojo is another example of a big library which could benefit greatly from TypeScript but also makes extensive use of mixins. It is not uncommon for classes in an application to require the use of 3 extra mixins. Using Dojo with TypeScript isn't really practical at the moment.

@nycdotnet
Copy link

Just chiming in that this would be really useful. I had requested something like this on CodePlex last September.

https://typescript.codeplex.com/workitem/1631

@basarat
Copy link
Contributor

basarat commented May 7, 2015

@duanyao
Copy link

duanyao commented Jun 5, 2015

I love this suggestion because it is also useful for creating UniversalElement (#3304).

@benliddicott
Copy link

Is this need met by Intersection Types #3622 (committed)? See also Feature: type alternatives (to facilitate traits/mixins/type decorators) #727, aiming at the same target.

Syntax: A & B is a type which implements both A and B. (In other words roughly equivalent to interface A_n_B implements A, B{}) It can be used wherever union types can be used.

Synopsis:

interface Emitter{ 
  on(event, fn);
  once(event, fn);
  off(event, fn);
  emit(event);
}

function MakeEmitter<T>(o T: ( T & Emitter) {
   require('my-emitter-library').mixin(o);
   return <T & Emitter>o;
}

Or you could provide a .d.ts which typed the function mixin as returning the appropriate intersection type.

@unional
Copy link
Contributor

unional commented Jun 27, 2016

How about this use case?
I can't find a way to do this:

interface Foo {
  foo: string;
}

interface Boo extends Foo? {
  boo: string;
}

let x: Boo = getBoo();
if (x.foo) {
  let y: Foo = x;
  ...
}

Essence: I want to put make foo and optional property in Boo, but still able to type y properly.

Here is an example that show current error when this is missing:

interface Config<S> {
  initialize<S>(...);
  reduce<S>(state: S, action: any): S;
  ...
}

class Foo<S> {
  configs: Config<S>[] = [];
  reducers: Config<S>[] = []; // <-- Could have been Reducer<S>[];
  addConfig(config: Config<S>) {
    this.configs.push(config);
    if (config.reduce) {
      this.reducers.push(config);
    }
  }

  do(state: S, action: any) {
    return this.reducers.reduce((s, reducer) => {
      // Object is possibly 'undefined' (with `strictNullCheck`)
      return reducer.reduce(s, action);
    }, state);
  }
}

Currently a workaround is to duplicate the signature of reduce() in interface Reducer<S> { } and make it not optional.

@guillaume86
Copy link

It's also useful for options parameters or stricts merge typechecks.

A very common pattern is to define default options object with values for all required properties
and when the user provide his options, do something like Object.assign({}, defaults, userOpts).

It would be nice to internally use IOptions with some required properties, and expose the API to the user with IOptions? (all properties optional).

This would also be useful in redux when Object.assign is used as an immutable merge.

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Oct 24, 2016

This kind of type operator would be extremely useful in many cases as discussed above. One that occurs to me and I don't think it's been mentioned, is simply sending and receiving isomorphic data from a JSON service.

Generally when you do this you want anothing interface with optional properties to compose requests and the exact same interface except with required properties to type responses.

function getUser(id: number): Promise<User>;

function updateUser(user: User?): Promise<void>;

It would be especially powerful when mixed with subset types and intersection types.

I don't think it should be limited to interfaces I think it should apply to type aliases.

@RyanCavanaugh
Copy link
Member

This is now

class Bar {
  // Bar does not have a foo property.
}
interface Bar extends Partial<IFoo> { }

new Bar().foo;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests