microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.69k stars 12.44k forks source link

Partial Types (Optionalized Properties for Existing Types) #4889

Closed Gaelan closed 7 years ago

Gaelan commented 9 years ago
// Given:
interface Foo {
    simpleMember: number;
    optionalMember?: string;
    objectMember: X; // Where X is a inline object type, interface, or other object-like type 
}

// This:
var foo: partial Foo;
// Is equivalent to:
var foo: {simpleMember?: number, optionalMember?: string, objectMember?: X};

// And this:
var bar: deepPartial Foo;
// Is equivalent to:
var foo: {simpleMember?: number, optionalMember?: string, objectMember?: deepPartial X};

Potential Use Cases

DanielRosenwasser commented 9 years ago

I brought this up recently with @mhegazy, and it can really come in handy, especially

interface Props { /*...*/ };

class Foo implements Props {
     constructor(opts: partial Props) { /*...*/}
}
let NewThing: NewMembers & (partial ThingImMixingInWith) = { /*completionHere*/

And I'm sure in other scenarios. How we choose to go about making this available is a separate matter.

s-panferov commented 9 years ago

Just for bookkeeping, it's the same as $Shape from https://github.com/Microsoft/TypeScript/issues/2710.

Gaelan commented 9 years ago

@s-panferov Does $Shape match deepPartial or partial? (deepPartial is recursive for sub-objects)

kitsonk commented 9 years ago

When you want completion from members you're going to mix in but you don't want to implement all the required members.

And type guard against anything that isn't actually in the interface.

I would suspect $Shape could match partial at the least if not deepPartial (e.g. a non-strict object literal check).

Why wouldn't the behaviour always just be like deepPartial... If you are already in the mode of not being strict, why would you ever suddenly not want that to be deep?

Gaelan commented 9 years ago

@kitsonk I was thinking of React.setState, or any other function that overwrites keys of one object with keys from another, not recursing.

jbondc commented 9 years ago

This would work nicely:

type foo = {a?:string, b?:string}; 
type fooStrict = foo & any!optional; // {a: string, b: string}
type fooOptional = foo & any!strict; // {a?: string, b?: string}
fredgalvao commented 9 years ago

This would make typings for lodash/underscore pretty awesome.

Would also be usefull in cases where you get a partial model as a param on the constructor to build the actual model. This way we wouldn't need to create a sibling interface just to hold the same properties with different optionality.

class Person {
    name: string;
    surname: string;
    birthday: Date;
    numberOfThings: number;

    constructor(initialValues: partial Person) {
        this.name = initialValues.name;
        this.surname = initialValues.surname;
        this.birthday = initialValues.birthday;
        this.numberOfThings = initialValues.numberOfThings;
    }
}

new Person({name: 'Fred', surname: 'Galvão', numberOfThings: 2});
dallonf commented 9 years ago

Yes, this would be extremely handy! Another good use case is React "higher-order" components - I want to implement a component that acts just like another, "lower-order" component, except it automatically calculates the value of some of its props... example time:

interface IUserAvatarProps {
  url: string,
  size?: number
}

class UserAvatar extends React.Component<IUserAvatarProps, {}> {
  //...
}

interface ISmartUserAvatarProps implements partial IUserAvatarProps {
  userId: string
}

class SmartUserAvatar extends React.Component<ISmartUserAvatarProps, {avatarUrl: string}> {
  render() {
    return <UserAvatar url={this.state.avatarUrl} {...this.props} />;
  }

  componentWillMount() {
    // Fetch this.state.avatarUrl from this.props.userId
  }
}

// Later...
<SmartUserAvatar id="1234" size={32} />
DomBlack commented 8 years ago

Yet another use case would be backbone models which have defaults and the constructor takes a partial set of attributes which override the defaults:

interface CarData { make: string, model: string }

class Car extends Backbone.Model<CarData> {
  defaults: CarData = {
    make: 'BMW',
    model: '7'
  }
}

new Car({ model: 'X5' });
jbrantly commented 8 years ago

@DomBlack just pointed me to this. Thanks! Just for backward/forward reference, this was also discussed in #392.

Strate commented 8 years ago

+1

xogeny commented 8 years ago

+1

For me, the big benefit of such functionality would be in being able to create typesafe APIs around the various libraries for immutable data structures like updeep or immutable.js. In both of these libraries, the efficiency of the library hinges on being able to modify data structures by passing in the "deltas" you wish to apply. And the fundamental issue with type safety is a way of expressing the types of these deltas in the context of a parametric type for the complete data.

Of course, some basic language level functionality for implementing or expressing lenses would also be a potential way of addressing the issue.

masbicudo commented 8 years ago

There is a use case for the Object.assign (from ES6).

It could be defined using partial types like this:

function assign<T>(target: subset of T, ...sources: (subset of T)[]): T;

Now I could do this:

var x = Object.assign({name:"Miguel Angelo"}, {age:32});

As all arguments are stated to be subsets of T, the call expression type could be inferred to be:

interface __anonymous {
  name: string;
  age: number;
}

That would be assignable to the following type:

interface IPerson {
  name: string;
  age?: number;
}

It'd be assignable because __anonymous is a super set of IPerson.

If this was possible, I could pass a variable y of type IPerson to the assign method. The following pattern is useful when changing an immutable object:

var x = Object.assign({}, y, {name:"Miguel Angelo"});

As y is not known to have age or not, the resulting inferred type would have an optional age, resulting in a type that is equal to the type IPerson itself. The inference process could see that, and infer that this call expression type really is IPerson.

The final case, would be when I state the type of T when calling. The inference algorithm could then make sure that the resulting type is what I expect, or give me an error:

var x = Object.assign<IPerson>({name:"Miguel"}, {age:32}); // the combined subsets is a valid IPerson
var x = Object.assign<IPerson>({}, {name: "Miguel"}); // the combined subsets is a valid IPerson
var x = Object.assign<ILawyer>(new Lawyer(), new Person()); // the combined subsets is a valid ILawyer
var x = Object.assign<IPerson>({}, {age: 30}); // ERROR: the combined subsets is a valid Person

For the keyword, it could be partial, subset of, or anything, but I propose it to be subset of, because then there could be a superset of. But then partial could have a complete counterpart... I don't know... anything is good enough.

Does this make sense?

tomsdev commented 8 years ago

Sorry @masbicudo if I didn't understand your example correctly but I think it can already be typed using "type parameters as constraints", see the first post in https://github.com/Microsoft/TypeScript/pull/5949

jcristovao commented 8 years ago

I've done this as a test, and I am pretty excited with the possibilities this seems to open.

interface xpta 
  { a : number
  , b : string
  , c : number[]
  }

interface xptb
  { a : number
  , b : string
  }

function assertSubType<T extends U, U>(x: T, y: U):void { };

assertSubType(<xpta>null,{a: 6, b:'aa'}); /* compiles, does nothing as expected */
assertSubType(<xpta>null,{a: 6, d:4});     /* gives error in typescript */
assertSubType(<xpta>null,{a: 6, c:'aa'}); /* gives error in typescript */

Is it related?

guncha commented 8 years ago

With the latest 1.8-beta, it's possible to get this to work when both type parameters are defined on the function. Unfortunately, I doesn't seem to solve the React.Component.setState use case as it is using generic type classes.

I.e.:

declare function setState<T, S extends T>(state: S, partial_state: T) // works!

interface Component<S> {
  state: S
  setState<T, S extends T>(partial_state: T) // doesn't work as S is considered a new type param
}
hayeah commented 8 years ago

I have the same issue as @guncha when trying to type a cursor structure

interface Cursor<T> {
  get(): T,
  set(data: T)

  merge<T extends U, U>(val: U)
}

+1

mjohnsonengr commented 8 years ago

Agree with @masbicudo about Object.assign() use case +1

basarat commented 8 years ago

@mjohnsonengr since TypeScript 1.8 Object.assign has a nice type definition :rose:

    /**
      * Copy the values of all of the enumerable own properties from one or more source objects to a 
      * target object. Returns the target object.
      * @param target The target object to copy to.
      * @param source The source object from which to copy properties.
      */
    assign<T, U>(target: T, source: U): T & U;

    /**
      * Copy the values of all of the enumerable own properties from one or more source objects to a 
      * target object. Returns the target object.
      * @param target The target object to copy to.
      * @param source1 The first source object from which to copy properties.
      * @param source2 The second source object from which to copy properties.
      */
    assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;

    /**
      * Copy the values of all of the enumerable own properties from one or more source objects to a 
      * target object. Returns the target object.
      * @param target The target object to copy to.
      * @param source1 The first source object from which to copy properties.
      * @param source2 The second source object from which to copy properties.
      * @param source3 The third source object from which to copy properties.
      */
    assign<T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;

    /**
      * Copy the values of all of the enumerable own properties from one or more source objects to a 
      * target object. Returns the target object.
      * @param target The target object to copy to.
      * @param sources One or more source objects from which to copy properties
      */
    assign(target: any, ...sources: any[]): any;

I might have read your question wrong. Just thought I'd try to be helpful still :rose:

mjohnsonengr commented 8 years ago

@basarat that works except I would still have to define each partial interface as in the below example. I suppose it's just a convenience factor, but it would be nice if I didn't have to define PersonName to get typing information on the name in the second arbitrary object. Having the second interface just seems like a nightmare to maintain.

interface Person {
    name: string;
    age: number;
}

interface PersonName {
    name: string;
}

var person = { name: "asdf", age: 100 };
Object.assign<Person, PersonName>({}, person, { name: "mjohnsonengr" })

I guess a possible shortcut is

var updatePersonName = (person: Person, name: string) => Object.assign({}, person, { name });
basarat commented 8 years ago

@mjohnsonengr I blindly copy pasted from lib.d.ts without actually looking at it. I should have linked to : https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#type-parameters-as-constraints :rose:

function assign<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = source[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 });  // Error
mjohnsonengr commented 8 years ago

@basarat Ohh! Now that's fancy! I'll have to play with that. Thanks for pointing that out to me!!

jcristovao commented 8 years ago

One question, given this modified example from (my own example) above:

interface A 
  { a : number
  , b : string  }

interface O // optional additional field
  { a : number
  , o? : string  };

interface M // mandatory additional field
  { a: number
  , m: string };

function assertSubType<T extends U, U>(x: T, y: U):void { };

assertSubType(<A>null,<M>null); // gives error as expected
assertSubType(<A>null,<O>null); // does not give an error

So, my question is, is an object with an optional field not included on the 'extended object' a valid subtype?

stepancar commented 8 years ago

@basarat this good But we need Somthing like this

class MyGenericClass<S>{
     setState<U => S extends U >(state: U, callback?: ()=>void): void
}
var myInstance = new MyGenericClass<{a: string; b: number; c: Array<string>}>();
myInstance.setState({a: 'any value'}); // ok
myInstance.setState({a: 'any value', c: []}); // ok
myInstance.setState({a: 'any value', c: [2,3,4]}); // error
myInstance.setState({a: 'any value', b: 'any not number val'}); // error

In this example we added condition for U type

What do you think about it?

stepancar commented 8 years ago

@basarat

//like assign 
class MyGenericClass<S>{
     setState<U, S extends U >(state: U, callback?: ()=>void): void
}
but now S not reffers to Generic param of class
amir-arad commented 8 years ago

how about adding java's super keyword?

class MyGenericClass<S>{
     setState<U super S >(state: U, callback?: ()=>void): void
}
malibuzios commented 8 years ago

This looks to me like a particular application for some more generalized concept of a compile-time type transformation function. I'm not sure if the "partial type" naming successfully captures what it actually does (I would expect a "partial" type to be simply a type that is declared across multiple files as in C#).

I think a more appropriate name for these transformations may be something like optionalize and deepOptionalize respectively (there may be a better name but that's the most reasonable one I found so far).

So for this interface:

interface MyInterface {
  prop: number;
  optionalProp?: string;
  obj: SomeOtherInterface;
}

The transform could be applied as a (built-in) compile-time function, which would accept a type and returns a type, and would only work at type positions, e.g:

type WeakenedMyInterface = optionalize(MyInterface);
type DeeplyWeakenedMyInterface = deepOptionalize(MyInterface);

let x: optionalize(MyInterface);

function func(arg: deepOptionalize(MyInterface)) {
   ...
}

("Optionalize" seems to be a valid English word meaning "to make optional" according to the Oxford Dictionary)

amir-arad commented 8 years ago

@malibuzios what about generics? is that what you're proposing?

class MyGenericClass<S>{
     setState(state: Optionalize(S), callback?: ()=>void): void
}
malibuzios commented 8 years ago

@amir-arad

I believe it is too early to call this a 'proposal' but I think this notation could also work with generic parameters as well. I changed the casing to optionalize to reflect the more common convention for functions though (so it doesn't get confused with a type).

There will be types that are not really applicable here like null, undefined, number, string etc. so it may (or may not depending on what works best) require a generic constraint like:

class MyGenericClass<S extends object>{
     setState(state: optionalize(S), callback?: ()=>void): void
}

(by object here I meant the type proposed in #1809, which isn't implemented yet)

dallonf commented 8 years ago

The idea of type functions reminds me of Flow's experimental (and almost completely undocumented) $Keys<>, $Subtype<>, $Diff<>, etc. types.

If we're going that far... it might be worth considering the other use case which would benefit from compile-time post-processing of types: ImmutableJS (and similar libraries).

It would be really nice to define something like let foo: ImmutableRecord<{x: string}> and have foo.set('x', 'bar') automatically typechecked as a string. Not sure exactly what this would look like in practice or in the type definitions, though.

malibuzios commented 8 years ago

@dallonf

The syntax I've suggested was only meant to create the 'analogy' or 'sense' of a function. It is not actually a user-definable function but a built-in language construct with function-like syntax.

I felt that having function-like syntax would demonstrate the semantics here better - which is the application of a transformation to an interface type. Additionally this opens some space to further syntax that may be added in the future of this kind (e.g. doSomething(MyType), doSomethingElse(MyType) .

amir-arad commented 8 years ago

@malibuzios We've open sourced Tspoon, our Typescript AST visitors engine. We use it to manipulate Typescript's semantics. Want to use it to try and implement optionalize? I'd be glad to show you the ropes.

malibuzios commented 8 years ago

@amir-arad

Interesting, I'll take look at it when I have time, though I'm not that familiar with the compiler internals and API (I guess at some point it would seem reasonable for me to learn, though).

Actually I recently noticed there are other relatively simple type transformations I found to be useful and important for me right now (and may be for others) :

  1. The ability to create an interface from only the public members of a class (see this entry in the FAQ for more info).
  2. The ability to create an interface from an abstract class and remove the "heritage" of the abstract methods (there's a strange behavior that causes this and I need to investigate more - it could be a bug though).
  3. Create a "nominal" interface from an existing interface, without adding any "dummy" properties (may be possible to do).

I've considered writing an experimental proposal for user-defined compile-time type transformations.. (at least to exchange some ideas, not necessarily to have it implemented any time soon - I understand it could be problematic for many reasons, see more below)

I even considered a syntax (for illustration purposes only!) inspired by the type alias syntax, something like:

// Returns the same type where all properties are made optional
type optionalize(typeMetadata: TypeMetadata): TypeMetadata {
    for (let propertyName in typeMetadata.properties)
        typeMetadata.properties[propertyName].isOptional = true;

    return typeMetadata;
}

// Returns the same type where all properties are made optional, recursive
type deepOptionalize(typeMetadata: TypeMetadata): TypeMetadata {
    for (let propertyName in typeMetadata.properties) {
        let currentPropertyMetadata = typeMetadata.properties[propertyName];

        currentPropertyMetadata.isOptional = true;
        if (currentPropertyMetadata.isObject)
            deepOptionalize(currentPropertyMetadata)
    }

    return typeMetadata;
}

The results of this could be somehow cached, so compilation performance is not necessarily a problem. Executing at compile time wouldn't be as well (the compiler is running in Javascript VM after all). Error reporting shouldn't also be a problem, I believe.

The "real" potential problems would be what would the compiler do to avoid and detect infinite execution, stack overflows, large memory consumption etc. It could also be that the scope of this would simply appear too narrow and something closer to AST transforms would seem to be more widely useful.

AlexGalays commented 8 years ago

Another use case: React's getDefaultProps() or Component.defaultProps. These properties are of the type subset of Props.

dallonf commented 8 years ago

React's default props are actually an even more complex case than that - the external facing props type is mixed with it! Err, that doesn't make any sense to describe it, lemme write some code:

interface Props {
  x: string;
  y?: string;
}

class MyComponent extends React.Component<Props, {}> {
  // Fulfills a required property!
  static defaultProps = {
    x: "foo"
  };

  render() { /* this.props will be of type Props */ }
}

// Later...
ReactDOM.render(<MyComponent />, document.getElementById('app'));

That last line should be perfectly legal, because its defaultProps fulfills the requirement of x. In other words, outside of the component, its props type is {x?: string, y?: string}.

Not sure if that's relevant to the current discussion but I thought it was worth bringing up.

ghost commented 8 years ago

Another potential use case here, although I think everyone is really on the same page. I think that the constructor options example @DanielRosenwasser gave will be the most beneficial.

Here's the default lib.es6.d.ts interface for CSSStyleDeclaration:

interface CSSStyleDeclaration {
    alignContent: string;
    alignItems: string;
    alignSelf: string;
    alignmentBaseline: string;
    animation: string;
    animationDelay: string;
    animationDirection: string;
    animationDuration: string;
    animationFillMode: string;
    animationIterationCount: string;
    etc: string;
    getPropertyPriority(propertyName: string): string;
    [index: number]: string;
}

It would be nice to be able to use that object for setting inline style objects, without having to apply it fully.

And, this is sprawling a little (or a lot), but what do you think about the ability to remove props from a partial extend as well? Too much?

interface StyleObject partial CSSStyleDeclaration {
    -getPropertyPriority;
}

const x: StyleObject = { // valid partial match
    alignContent: 'center',
    animationDelay: '1s'
};

const y: StyleObject = { // invalid, because getPropertyPriority is removed from StyleObject
    getPropertyPriority: (input: string) => {
        console.log(string);
        return string;
    }
};
dead-claudia commented 8 years ago

I have a use case for partial types: I'm working on a vdom library that allows partially applied nodes. I would literally need dependent types (or at least C++-style variadic types with specializations) to correctly type this case, but for practical reasons, I'd gladly accept partial types to ease the problem.

// This is completely wrong and too permissive for partially applied partial nodes.
type Base<T, U> = ComponentFactory<T, U> | PartialNode<T, U, any>;

// Supertype for component factories
interface ComponentFactory<T, U> {
    // Merge partially applied properties.
    merge?(initial: T, rest: T): void;
}

interface PartialNode<T, U, V> {
    mask: nodes;
    type: Base<T, U & V>;
    attrs: U;
}

export function part<T, U, V>(type: Base<T, U>, attrs?: U): PartialNode<T, U, V>;

// I'd be okay with this:
type Base<T, U> = ComponentFactory<T, U> | PartialNode<T, U>;

// Supertype for component factories
interface ComponentFactory<T, U> {
    // Merge partially applied properties.
    merge?(initial: partial T, rest: partial T): void;
}

interface PartialNode<T, U> {
    mask: nodes;
    type: Base<T, U>;
    attrs: partial U;
}

export function part<T, U>(type: Base<T, U>, attrs?: U): PartialNode<T, U>;
guncha commented 8 years ago

Type subtraction could be a useful feature, I've seen it required in a few places, mostly library functions. It will also be needed for ES7 type destructuring:

let {a, b, ...rest} = this.props
// type of rest here?

As for the syntax, I'd like to see something like type C = A - B which is more general than removing single properties.

dead-claudia commented 8 years ago

@guncha That includes React and Object.assign now, where React already uses spread attributes. And yes, type subtraction would be more general, but it's also a bit harder to implement. It is effectively the inverse of intersection, if my memory of set theory is correct.

Ciantic commented 8 years ago

I think partial of something has also hidden meaning: it must have at least one property implemented.

E.g. currently this is not an error, and a painful:

let a = "test";
let b: {
    hello?: string;
    what?: string;
}
b = a; // not an error, because {} is compatible with string

But consider this with partial:

let a = "test";
let b: {
    hello: string;
    what: string;
}
let c: partial typeof b
c = a; // this could be error!

Because partial could also mean that at least one of the properties are implemented, and "test" does not implement hello or what.

Then {} should not be compatible with any of the partial types.

ghost commented 8 years ago

@Ciantic It's surprising to me that string is assignable to {}, that seems strange. I would like it if {} was compatible with partial types because I'd like to be able to "build up" an object after instantiating it. E.g.

interface IFoo { foo: string; }
const foo: partial IFoo = {}; // foo does not yet implement IFoo, but will eventually
foo.foo = 'foo';
// maybe someday static analysis could determine that foo must fully implement IFoo at this point

Edit: Maybe {} is OK as a partial if explicitly cast (i.e. {} as partial IFoo).

ghost commented 8 years ago

How about this?

interface IFoo { foo: string; }
let foo: partial IFoo;
let bar: IFoo;
foo = {}; // not ok, {} does not implement any members of IFoo
foo = {} as partial IFoo; // ok, explicit cast 
bar = foo; // not ok, partial IFoo does not implement IFoo
bar = foo as IFoo; // ok, explicit cast (maybe a warning?)
foo.foo = 'foo';
bar = foo; // ok, static analysis shows foo implements IFoo now
unional commented 8 years ago

there seems to be a lot of good things going on in this thread! 🌷

Here is another use case relate to option bag:

interface Bag {
  requireA: string;
  optionalB?: string,
}

class Base {
  constructor(options: Bag) { ... }
}
class Child extends Base {
  constructor(options: partial Bag) {
    options.requireA = options.requireA || 'child default value';
    super(options);
  }
}

...or if there is a better way to do this. 🌹

zpdDG4gta8XKpMCd commented 8 years ago

as a few people noticed, what you need isn't a partial types but a subtraction operation on types: V = T - U

speaking of which: https://github.com/Microsoft/TypeScript/issues/4183

unional commented 8 years ago

@aleksey-bykov Thanks for the pointer!

dead-claudia commented 8 years ago

@aleksey-bykov That will partially fix my particular case. What I need is the set difference between two object types, but with P below constrained to a partial subtype of A at minimum.

interface PartialNode<T, P, A> extends Base<T, A - P> {
    mask: nodes;
    type: Base<T, A>;
    attrs: P;
}

// Something like this, where P partially extends A
export function part<T, P extends partial A, A>(
  type: Base<P, A>,
  attrs?: P
): PartialNode<T, P, A>;

My case will also be difficult to infer, which is part of why I mentioned earlier that dependent types are probably the easiest thing to work with and while still keeping the types inferrable.

mpseidel commented 8 years ago

Running into the lack of partial typing more and more often. Would love to see a solution here.

OliverJAsh commented 8 years ago

This pattern is used a lot in Redux, where you have some object of type A that you want to clone and update properties on, e.g.

interface PartialState {
  foo?: string
}
interface State {
  foo: string
}

function update(patch: PartialState): State { return Object.assign({}, state, patch); }

const state = { foo: 'baz' };
const newState = update({ foo: 'bar' }, state);
jaen commented 8 years ago

Let me know if I'm talking nonsense, but as far as I understand

function exampleCovariant<Subtype extends Supertype>(arg: Subtype) { … }

is basically type parameter covariance — we expect Subtype to be… well, a subtype of Supertype, is that correct?

Then, wouldn't the requirement in question be solved by adding ability to specify contravariance constraints to type parameters, like so (syntax borrowed from Java, could be probably bikeshedded):

function exampleContravariant<Supertype super Subtype>(arg: Supertype) { … }

in which case the arg would be expected to be any appropriate supertype of Subtype. And since as far as I understand TypeScript's type system is structural, it would mean syntax and semantics of super constraint would admit code like so:

class Component<State>
{
    private _state: State;
    get state() { return this._state; }

    protected setState<NewState super State>(newState: NewState)
    {
        this._state = { ..._this.state, ...newState };
    }
}

interface CounterState
{
    count: number;
    interval: number;
}

class Counter extends Component<CounterState>
{
    private setIntervalHandle: any;

    get count() { return this.state.count; }

    constructor<StateSubset super CounterState>(initialState: StateSubset)
    {
       this.setState({count: 0, interval: 500, ...initialState});
       // calling `this.state({top: "kek"})` would've been illegal here since
       // it's not a supertype of `CounterState` and would produce
       // a compile-time error
    }

    start()
    {
        this.setIntervalHandle =
            setTimeout(() =>
                this.setState({count: this.state.count + 1})),
                this.state.interval;
    }

    stop()
    {
        clearTimeout(this.setIntervalHandle);
        this.setIntervalHandle = null;
    }
}

which as far as I understand was one of the motivating examples for this proposal.

Why I think it's better than partial? Simple consistency and symmetry — if generics admit covariance bounds and contravariance seems to solve this problem, why not admit contravariance bounds as well. That said this alone would not be able to type Object.assign for example, it would require variadic generics as well (which might be perceived as a downside, but to me variance + variadic generics seems like the proper solution).

Is there any downside I'm missing here? Maybe it's harder to infer than partial-ness?

dead-claudia commented 8 years ago

That sounds acceptable to me, as long as it remains clear it's not a solution to #1394 (this is method variance, not type variance).

On Thu, Sep 8, 2016, 03:17 Tomek Mańko notifications@github.com wrote:

Let me know if I'm talking nonsense, but as far as I understand

function exampleCovariant(arg: Subtype) { … }

is basically type parameter covariance — we expect Subtype to be… well, a subtype of Supertype, is that correct?

Then, wouldn't the requirement in question be solved by adding ability to specify contravariance constraints to type parameters, like so (syntax borrowed from Java, could be probably bikeshedded):

function exampleContravariant(arg: Supertype) { … }

in which case the arg would be expected to be any appropriate supertype of Subtype. And since as far as I understand TypeScript's type system is structural, it would mean syntax and semantics of super constraint would admit code like so:

class Component { private _state: State; get state() { return this._state; }

protected setState<NewState super State>(newState: NewState)
{
    this._state = { ..._this.state, ...newState };
}

} interface CounterState { count: number; interval: number; } class Counter extends Component { private setIntervalHandle: any;

get count() { return this.state.count; }

constructor<StateSubset super CounterState>(initialState: StateSubset)
{
   this.setState({count: 0, interval: 500, ...initialState});
   // calling `this.state({top: "kek"})` would've been illegal here since
   // it's not a supertype of `CounterState` and would produce
   // a compile-time error
}

start()
{
    this.setIntervalHandle =
        setTimeout(() =>
            this.setState({count: this.state.count + 1})),
            this.state.interval;
}

stop()
{
    clearTimeout(this.setIntervalHandle);
    this.setIntervalHandle = null;
}

}

which as far as I understand was one of the motivating examples for this proposal.

Why I think it's better than partial? Simple consistency and symmetry — if generics admit covariance bounds and contravariance seems to solve this problem, why not admit contravariance bounds as well. That said this alone would not be able to type Object.assign for example, it would require variadic generics as well (which might be perceived as a downside, but to me variance + variadic generics seems like the proper solution).

Is there any downside I'm missing here? Maybe it's harder to infer than partial-ness?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-245513912, or mute the thread https://github.com/notifications/unsubscribe-auth/AERrBH7wdYO_EmS5-7i0391pjPkoXtaBks5qn7aAgaJpZM4GAkXw .