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

Partial Types (Optionalized Properties for Existing Types) #4889

Closed
Gaelan opened this Issue Sep 20, 2015 · 86 comments

Comments

Projects
None yet
@Gaelan

Gaelan commented Sep 20, 2015

// 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

  • Mongo Queries (deepPartial)
  • React.setState (partial)
@DanielRosenwasser

This comment has been minimized.

Member

DanielRosenwasser commented Sep 21, 2015

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

  • When a constructor takes an options bag that has its own properties
interface Props { /*...*/ };

class Foo implements Props {
     constructor(opts: partial Props) { /*...*/}
}
  • When you want completion from members you're going to mix in but you don't want to implement all the required members.
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

This comment has been minimized.

s-panferov commented Sep 21, 2015

Just for bookkeeping, it's the same as $Shape from #2710.

@Gaelan

This comment has been minimized.

Gaelan commented Sep 21, 2015

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

@kitsonk

This comment has been minimized.

Contributor

kitsonk commented Sep 21, 2015

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

This comment has been minimized.

Gaelan commented Sep 21, 2015

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

@jbondc

This comment has been minimized.

Contributor

jbondc commented Sep 22, 2015

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

This comment has been minimized.

fredgalvao commented Sep 23, 2015

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

This comment has been minimized.

dallonf commented Oct 12, 2015

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

This comment has been minimized.

DomBlack commented Oct 29, 2015

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

This comment has been minimized.

jbrantly commented Oct 29, 2015

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

@Strate

This comment has been minimized.

Strate commented Nov 11, 2015

+1

@xogeny

This comment has been minimized.

xogeny commented Nov 14, 2015

+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

This comment has been minimized.

masbicudo commented Jan 14, 2016

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

This comment has been minimized.

tomsdev commented Jan 18, 2016

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 #5949

@jcristovao

This comment has been minimized.

jcristovao commented Jan 29, 2016

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

This comment has been minimized.

guncha commented Feb 2, 2016

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

This comment has been minimized.

hayeah commented Feb 11, 2016

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

This comment has been minimized.

mjohnsonengr commented Feb 25, 2016

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

@basarat

This comment has been minimized.

Contributor

basarat commented Feb 25, 2016

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

    /**
      * 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 🌹

@mjohnsonengr

This comment has been minimized.

mjohnsonengr commented Feb 26, 2016

@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

This comment has been minimized.

Contributor

basarat commented Feb 26, 2016

@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 🌹

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

This comment has been minimized.

mjohnsonengr commented Feb 26, 2016

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

@jcristovao

This comment has been minimized.

jcristovao commented Mar 8, 2016

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?

@olmobrutall

This comment has been minimized.

olmobrutall commented Sep 20, 2016

Got it! This compiles

var temp = { name: "John", age: 13, gender: "Male" };
var john: { name: string; age?: number } = temp;

So TS uses the heuristic that if you are using a literal object you don't want inheritance stuff, preventing errors. I think this will work for 99% of the setState situations as well.

If this is a problem:

interface MyState {
  width: number;
  size: string;
}
function setState(x: partial MyState) { /* ... */ }
let newOpts = { width: 10, length: 3 }; // Incorrect name 'length'
setState(newOpts); // OK but suspicious if not wrong

then this is also a problem that we have today:

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

function savePerson(x: Person) { /* ... */ }
var temp = { name: "John", age: 13, gender: "Male" };
savePerson(temp)

In practice this error doesn't happen because you want to declare the type as soon as possible to get auto-complete for the members.

@olmobrutall

This comment has been minimized.

olmobrutall commented Sep 20, 2016

Just to clarify, I'm not suggesting that partial should include sealed automatically, but to skip (or delay) sealed for now.

If in the future sealed becomes more of an issue we'll be in a good position to add it, with no breaking behavior.

@joewood

This comment has been minimized.

joewood commented Sep 23, 2016

Now that 2.0 is out, will this be added to the 2.1 roadmap? I don't see it there currently.

@saschanaz

This comment has been minimized.

Contributor

saschanaz commented Sep 27, 2016

How will partial keyword play well with partial class(#563), although the latter does not exist on TS?

partial class A { // adding on class A
}
partial interface A { // not adding on interface A but makes properties optional
}
@mDibyo

This comment has been minimized.

mDibyo commented Nov 17, 2016

FYI: Looks like this has been resolved with #12114.

@RyanCavanaugh

This comment has been minimized.

Member

RyanCavanaugh commented Nov 17, 2016

Indeed

@cjbarth

This comment has been minimized.

cjbarth commented Nov 17, 2016

How do mapped types fill the need of the various partial examples shown above?

@isiahmeadows

This comment has been minimized.

Contributor

isiahmeadows commented Nov 17, 2016

@cjbarth See here for some examples. It's pretty much a giant sledgehammer solving several constraint problems at once.

// Example from initial report
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};

// Partial<T> in that PR is defined as this:
// Make all properties in T optional
interface Partial<T> {
    [P in keyof T]?: T[P];
}
@mhegazy

This comment has been minimized.

Contributor

mhegazy commented Nov 18, 2016

Please note that Partial<T> is now part of the default library file.

@mpseidel

This comment has been minimized.

mpseidel commented Nov 18, 2016

You guys are all wizards to me. Hats off!

@xogeny

This comment has been minimized.

xogeny commented Nov 28, 2016

@mhegazy When you say it is in the "default library file", does that mean I should be able to use it with TS 2.1.x? Do I need to import something? It doesn't seem to work.

@DanielRosenwasser

This comment has been minimized.

Member

DanielRosenwasser commented Nov 28, 2016

@xogeny yes, keep an eye out for it in TypeScript 2.1 (or in our nightly builds).

@xogeny

This comment has been minimized.

xogeny commented Nov 28, 2016

@DanielRosenwasser I'm still confused. As far as I can tell, I'm running TypeScript 2.1 and I don't see it. Are you saying this will come out in a patch release of 2.1?

@mhegazy

This comment has been minimized.

Contributor

mhegazy commented Nov 28, 2016

typescript@2.1.1 is TS 2.1 RC. this does not have this change.
TS 2.1 will be typescript@2.1.3 which has not shipped yet. you can use typescript@next today to get this feature working.

@xogeny

This comment has been minimized.

xogeny commented Nov 28, 2016

Ah! OK, now I understand. I didn't realize these were release candidates. It would be much clearer if the version number reflected that, but I trust you had a good reason for doing it this way. I look forward to the final version!

@kitsonk

This comment has been minimized.

Contributor

kitsonk commented Nov 29, 2016

From npm view typescript:

{ name: 'typescript',
  description: 'TypeScript is a language for application scale JavaScript development',
  'dist-tags': 
   { latest: '2.0.10',
     next: '2.2.0-dev.20161129',
     beta: '2.0.0',
     rc: '2.1.1',
     insiders: '2.0.6-insiders.20161017' }
}

From a release tag perspective on npm, it is labelled a RC and will not install automatically.

Also the commit tag in GitHub also refects the nature of the release: https://github.com/Microsoft/TypeScript/releases/tag/v2.1-rc.

I believe the challenge is that some of the wider tooling requires installable versions to have a proper npm semver number (e.g. #.#.#).

@danielmeza

This comment has been minimized.

danielmeza commented Feb 8, 2017

Another good approach is to auto generate files without break the user code, then we can auto generate code using transformation templates.

@Microsoft Microsoft locked and limited conversation to collaborators Jun 19, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.