Partial Types (Optionalized Properties for Existing Types)
Gaelan opened this issue Β· 86 comments
// 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)
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.
Just for bookkeeping, it's the same as $Shape from #2710.
@s-panferov Does $Shape match deepPartial or partial? (deepPartial is recursive for sub-objects)
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?
@kitsonk I was thinking of React.setState, or any other function that overwrites keys of one object with keys from another, not recursing.
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}
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});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} />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' });+1
+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.
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?
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
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?
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
}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
Agree with @masbicudo about Object.assign() use case +1
@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 πΉ
@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 });
@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@basarat Ohh! Now that's fancy! I'll have to play with that. Thanks for pointing that out to me!!
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?
@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'}); // errorIn this example we added condition for U type
What do you think about it?
//like assign
class MyGenericClass<S>{
setState<U, S extends U >(state: U, callback?: ()=>void): void
}
but now S not reffers to Generic param of classhow about adding java's super keyword?
class MyGenericClass<S>{
setState<U super S >(state: U, callback?: ()=>void): void
}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)
@malibuzios what about generics? is that what you're proposing?
class MyGenericClass<S>{
setState(state: Optionalize(S), callback?: ()=>void): void
}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)
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.
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) .
@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.
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) :
- The ability to create an interface from only the public members of a class (see this entry in the FAQ for more info).
- 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).
- 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.
Another use case: React's getDefaultProps() or Component.defaultProps. These properties are of the type subset of Props.
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.
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;
}
};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>;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.
@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.
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 stringBut 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.
@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 pointEdit: Maybe {} is OK as a partial if explicitly cast (i.e. {} as partial IFoo).
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 nowthere 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. πΉ
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: #4183
@Aleksey-Bykov Thanks for the pointer!
@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.
Running into the lack of partial typing more and more often. Would love to see a solution here.
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);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?
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
#4889 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBH7wdYO_EmS5-7i0391pjPkoXtaBks5qn7aAgaJpZM4GAkXw
.
I would love to see this working, so many use cases for it. One is:
interface Test<A> {
myMethod: <E super A>(arg: E) => E;
}so that if I have:
interface Person extends Test<Person> {
name: string;
age: number;
}I would like to be able to call
person.myMethod({name: 'Luke'});
person.myProp({age: 344});
without having to make the properties of Person optional
Some thoughts here as I think I'm going to try to get this into 2.1 as part of a push to improve typing of widely-used libraries.
First, I don't think super is the right concept for this. super should logically mean a recursive supertype, meaning this code would typecheck even though it doesn't really meet the intent of the use cases described so far (shallow optionalizing is what's desired, not deep):
interface Options {
size: { width: number; height: number };
color: string;
}
function foo<T super Options>(opts: T) { }
foo({ size: { width: 32 } }); // Forgot 'height'So I think the next logical alternative is the "Make all properties optional" operator. I'll refer to that as partial T but am open to syntactic bikeshedding at a later date.
A big issue is that all-optional types (which is effectively what we're looking at synthesizing) behave very poorly in the type system. It's easy to have code like this:
interface Options {
width?: number;
size?: string;
}
function getOptions(): Options { return { width: 30, size: 'medium' } }
let x: Options = getOptions; // forgot to invoke, no error, lol wutThe "weak types" PR #3842 attempted to fix this by saying that all-optional properties (which I'll continue to refer to as "weak") can only be matched against types which have at least one declared property in common.
We could implement the "weak types" behavior for all types. I feel this is desirable, but it's maybe not sufficiently correct. Consider something like this:
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 wrongThis gets remarkably worse any time you have a name?: string in the target type because now any Function is a plausible match!
What setState really wants, I believe, is an optional sealed type which doesn't accept additional properties. This should be a shallow sealing. We could introduce an additional type operator (ΰ² _ΰ² ) sealed T which accepts any subtype of T which does not have additional declared (i.e. non-apparent in spec language) properties.
I haven't thought all the way through the implications of sealed as its own thing and am concerned it will suffer from the same "infectiousness" that non-null/readonly generally have (or be highly unsound) so that's maybe a last resort.
Alternatively, we could just say that any type produced by partial is implicitly sealed (and not provide the sealed type operator for the sake of reduced complexity). It's hard to imagine a case where you would want additional properties to show up in your target which weren't there in the first place.
Object.assign is much more complex because people use it to do both mixins/spreading and partial updates. Because the return value is unused (or at least ignorable), it's also hard to produce meaningful errors. Most likely we'll need to have Object.assign accept arbitrary types and return a spreaded type, and if you want to use it for partial updates you'll want to alias it through a const of a different, more specific type (e.g. const update: (x: T, ...extras: Array<partial T>) => T = Object.assign.bind(Object);)
Great summary there, @RyanCavanaugh.
Although I like the sealed concept on it's own, and would maybe find value in it by itself (being provided, that is), I can agree with leaving it implicit and internal for now. Having partial be the new operator, and have it incur sealedness seems a great path, satisfying technical and syntactic needs for me.
@RyanCavanaugh why do we need yet another special case when there is an elegant generic solution? i mean higher kinded types of course (#1213), please consider
type MyDataProto<K<~>> = {
one: K<number>;
another: K<string>;
yetAnother: K<boolean>;
}
type Identical<a> = a;
type Optional<a> = a?; // or should i say: a | undefined;
type GettableAndSettable<a> = { get(): a; set(value: a): void }
type MyData = MyDataProto<Identical>; // the basic type itself
type MyDataPartial = MyDataProto<Optional>; // "partial" type or whatever you call it
type MyDataGetterAndSetter = MyDataProto<GettableAndSettable>; // a proxy type over MyData
// ... etc
type MyDataYouNameItWhat = MyDataProto<SomeCrazyHigherKindedType>;@Aleksey-Bykov that's quite nice but with state / setState in React you'd likely initialize the state with its default values, thus not producing any named type which could be made generic.
i am sorry i am not that familiar with React, can the problem be stated independently? an example?
We (@dojo) don't use React either, though we have a similar concept/problem. There are situations where there is a use case where you have a full type, where it is valid to express valid sub-types which have a direct relationship with the full type.
From the generic concept of state perspective, you have an object which will get mixed into the target object. Where the target object may have required properties, but the partial mixed in object would only express the properties that need to be mutated or changed. This also applies to the generic concept of patching objects server side (including the RESTful concept of PATCH) which expresses mutations to be done on an object, of which those mutations can be functionally expressed at a partial object.
For example, given the following object:
interface Person {
firstName: string;
lastName: string;
amount: number;
}
const obj: Person = {
firstName: 'Bob',
lastName: 'Smith',
amount: 20
}I may functionally want to mutate/mixin/set state/etc. just the amount, but have it typed checked. Person is only valid though if it has all three properties. Currently the only way I can do that is by creating a separate "all optional" interface, which doesn't really properly express the right type, especially when there are covariant types:
interface PartialPerson {
firstName?: string;
lastName?: string;
amount?: number;
}
function patch(target: Parent, value: PartialPerson): Parent {
Object.assign(target, value);
}
patch(obj, { amount: 30 });Especially if I make changes to Person it gets even uglier. To properly model the type that I want to pass in the argument, I want so express something that says it is any valid partial sub-type of Person. There is no concept of classical inheritance in these sub-types, which is why I agree super is not the right concept here.
@Aleksey-Bykov is that independent enough?
React's setState is basically identical to what @kitsonk just posted in terms of behavior.
it looks like there are a number of independent obstacles standing on the way to "partial" types:
- to get and easily maintain a sub-interface (let's call it a patch) of the original interface (as was shown HKT can do it)
- have some a way to prevent unrelated (compared to the original interface) properties from being a part of a patch (still a question)
- anything else?
- How to deal with tagged union types:
type State = { type: 'foo'; value: number; } | { type: 'bar'; value: string; };
const target: State = { type: 'foo', value: 100 };
function patch(value: partial State): void {
Object.assign(target, value);
}
patch({ value: 'baz' }); // can't really type check this but breaks the type system
patch({ type: 'bar' }); // can't really type check this but breaks the type systemAs far as point 2, I would say excess properties in literals would not match the partial type, though interface assignability between non-literals would exist (e.g. essentially what we have had since 1.6).
Actually, have we defined what partial means with respect to unions? I think the naive thing to do would be to make the properties of partial T be the properties of T, adding undefined. That makes partial State useless as a tagged union: { type: 'foo' | 'bar' | undefined, value: number | string | undefined }, although it technically type checks.
You would notice the problem in a hurry if patch returned partial State, but a mutating function unfortunately swallows the type.
I don't see an easy solution to (3). We could start doing more complicated type algebra but so far we haven't. (For example, people would also like us to union call signatures on unions that have call signatures, but we don't.)
so to support 3 (discriminated unions) we need some properties to be optional and some to be still required, am i right? if so i got some good news, HKT can do it too:
type Kind = 'this' | 'that';
type MyDataProto<R<~>, O<~>> {
kind: R<Kind>;
one: O<string>;
another: O<number>;
}
type Identical<a> = a;
type Optional<a> = a?;
type MyData = MyDataProto<Identical, Identical>; // { kind: Kind; one: string; another: number }
type MyDataPatch = MyDataProto<Identical, Optional>; // { kind: Kind; one?: string; another?: number }am i still missing something?
My vote is for partial and sealed to be defined independently, I can imagine a non-sealed partial to still be of use (e.g. mixins). The issue with tagged unions is trickier, but I would be happy to reduce the scope if we could get this in 2.1. I could imagine eventually that the "tagged" field in a tag-union would be a mandatory part of the partial type.
HKT looks interesting - and I guess that would work with setState - although not crazy for the syntax. The only problem here is that you would need to define upfront your type to be enabled to be treated as partial. The advantage of a type modifier is that it can be applied to any type.
Just to be sure, would that new operator work with deep objects too? It would be quite limited otherwise.
Example:
deepUpdate(
{ a: { b: 33, c: 22, z: [1, 2] }, d: 'ok' },
{ a: { b: 44, z: [] } }
)
Also, yes the Optional operator must be usable dynamically, so that we can derive Optional types for any type (including parameterized types as is needed in that deepUpdate function definition)
@AlexGalays I think that's actually explicitly not what most people want (see above discussion on super vs partial). That isn't how Object.assign or React.setState work, for example.
@RyanCavanaugh Any chance we can have both? e.g deepPartial
Anything is possible but let's discuss in a separate issue π
I don't really get why sealed will be needed here. Extra members are already forbidden isn't it?
If I write:
var john: { name: string; age?: number } = { name: "John", age: 13, gender: "Male" };I get
Error Build:Type '{ name: string; age: number; gender: string; }' is not assignable to type '{ name: string; age?: number | undefined; }'.
@olmobrutall only when the object is "fresh". If there's a level of indirection where the type is manifested into a declaration, those errors no longer occur. Refer to this example from my previous longer comment:
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 wrongGot 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 wrongthen 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.
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.
Now that 2.0 is out, will this be added to the 2.1 roadmap? I don't see it there currently.
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
}Indeed
How do mapped types fill the need of the various partial examples shown above?
@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];
}Please note that Partial<T> is now part of the default library file.
You guys are all wizards to me. Hats off!
@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.
@xogeny yes, keep an eye out for it in TypeScript 2.1 (or in our nightly builds).
@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?
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.
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!
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. #.#.#).
Another good approach is to auto generate files without break the user code, then we can auto generate code using transformation templates.