-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
add object initializers #8545
Comments
type assertion? function extend(doThis: DoThis): DoBoth {
return doThis as DoBoth;
} |
no, quite opposite first off it will break if you do as you say secondly, type assertions is a way for taking responsibility off the compiler, whereas I am looking for the opposite |
the only thing i can think of other than type assertion would be to spread in the other object. function extend(doThis: DoThis): DoBoth {
return {
z: undefined,
...doThis
};
} |
nice but what about primitives and functions in the |
Spread should be sugar for object.assign, so no non-enumrable and only own properties. Not sure if this is what you had in mind. |
now as i read closer it looks like a solution |
Object spread and rest is tracked by #2103 |
gonna need to reopen it one more useful scenario function initializeAsDoThis<T unlike null | undefined>(obj: T => DoThis) { // <-- possible syntax for object that needs to be initialized?
obj.x = undefined;
obj.y = undefined;
}
class C implements DoThis {
constructor() {
initializeAsDoThis(this);
}
} |
spread operator is only useful at creating new objects, but for the cases where we deal with an existing object we can't use it |
i am not sure i understand what this sample is meant to do any why it can not be expressed using existing constructs. |
problem:
workaround: none, we have to declare and initialize 200 properties by hand solution: with initilizers we could have 20 functions which we would call from the constructor, each initilizer would add 10 initialized properties of a corresponding interface, we need a way for TypeScript to acknowledge that these properties are there and the class should be considered fully initialized |
definition: object initializer - is a function/method with a parameter that has 2 types: in and out, at the call site the function expects a value of the in-type to be used as an arugument, after the function is called argument should be considered being of the out-type function initialize<a>(
value: a /* <-- in type */ => a & { x: number } /* <-- out type */
): void { // <-- hypothetical syntax
value.x = 100;
}
let value = {};
value.x; // <-- should not typecheck
initialize(value);
value.x; // <-- should typecheck |
so is this a different proposal for #8353? |
I think it is different. Main difference is that rather than trying to
|
we have talked about something similar proposals before; the main reason for aversion is complexity. once you mix in generics, these declarations become harder to read and understand. |
You can achive something similar with Mapped Types and Partials, according to this StackOverflow answer. |
@afnpires SO question is about different matters |
@Aleksey-Bykov can you clarify which aspects (if any) can't be accomplished today? |
yes please
example: type A = {}
const a: A = {};
type B = { x: number; y: number };
function foo(a: {}): void { // <-- need syntax to express the effect
a.x = 0;
a.y = 0;
}
const b: B = a; // <-- works because `a` is of type `B` now after `foo` is called on it |
the only way to accomplish it today is to use so-called "mixins" via classes (which is as ugly as my life) or hacking |
i looked at them the second one is what i am taking about but i dont like the syntax, and besides this issue is 12000 issues ahead of that one |
Hey @Aleksey-Bykov, do you have any suggestion for that syntax? |
@lilezek yes please: function initializeXY<T extends {}>(obj: T => T & {x: number; y: number;}): void {
obj.x = 0;
obj.y = 0;
} |
@Aleksey-Bykov That's good for extending, but I think it can't be used for reducing an object: const obj = {x: 15, y: 13};
delete obj.x; // Or a function that does the delete for you. After the Edit: I think I didn't understand your syntax but isn't that the syntax for a function rather than the syntax for an object? |
did you try this: function getridofY<T extends { y: unknown }>(obj: T => T & { y: never; }): void {
delete obj.y;
} |
Sorry, as I wrote in the edit in my comment: isn't that the actual syntax for functions? |
functions require a list of parameters which has to be enclosed into parenthesis |
Yes, that's correct, but these are so similar I think that it would be easy to be confused about that. Anyway, if you want I can put your suggestion in my issue description as an additional option. |
please do |
Before I do I need first some description about how it would work with that syntax. For instance, if you want to convert something from string to be a number: function normalizePhoneNumber<T extends {phone: string}>(obj: T => T & { phone: number }): void {
obj.phone = parseInt(obj.phone, 10);
} What type is obj inside the function? If it is |
that's a very good question,
|
In that case wouldn't be just more simple to use a syntax similar to the one I proposed? Using two different identifiers, with two different types that will be compiled in JavaScript as the same one? I don't know this much about TypeScript compiler, but I think that 2nd and 3rd point are harder to achieve than having two separate identifiers. |
i agree, it might be easier to achieve by using 2 different identifiers, question of the balance of the price tag of this feature vs. happiness of the developers who tend to like typing less |
on the second thought it might be more confusing to have a virtual identifier that has no meaning in the real code unless typescript does code rewriting by replacing
|
also it can be a burden for developers to pick a name for that extra identifier, in my experience picking a meaningful name is 20% of my day job |
I think your suggestion and mine are not comparable in size. While you use generics and that adds On the other hand, I agree that having a second, virtual identifier, can be confusing and it might be even against TypeScript goals. I'll try to think about another suggestion without a virtual identifier, but I wouldn't go either to use generics + a syntax that is pretty similar to arrow functions, plus having a flow analyser which could be hard to implement and that I that is still undefined how it should work. About choosing a name, you can choose an arbitrary style for the name of the second virtual identifier, such as |
generics are necessary because it's the way to generalize and express uncertainties and about your types, i can't see how you can go without them, you would have to reinvent them at some point or greately limit the scope of applicability of this feature flow analysis is already implemented, we just need to make use of it: declare var a: number | string;
a = 'hey';
const text: string = a; arrow syntax is already familiar to anyone who used callbacks |
since the syntax concerns the types (not the JS expressions) it can be literally anything: what's important is that syntax needs to be bound to the parameter in place |
While I agree with everything you said, I don't see the uncertainty of types to need the use of generics. For instance, using the syntax I proposed (I use this one because I don't have yet a better option), the types here are exact and not uncertain: interface Before {
address: string;
}
interface After {
addr: string;
}
function map(userb: Before then usera: After) {
usera.addr = userb.address;
delete userb.address;
} You know exactly what must be the type before and you know exactly what will it be after. And this could be mixed with generics if any of these types (the type before and the type after) are unkown: function inlineMap<T,U>(arrayB: T[] then arrayA: U[], mappingFunc: (T) => U) {
for (let i = 0; i < arrayB.length; i++) {
arrayA[i] = mappingFunc(arrayB[i]);
}
} |
problem is that in my use cases i don't know much if anything at all about what objects will be passed into my initializer function think of the mixin pattern, i want to turn my very own object into something that has
my point is that |
so you are proposing to pass a callback for mutating bare and by this i mean that in TypeScript rather than passing 10 callbacks along with your generics, you instead simply require not a bare generic but something that has |
besides say you have
now i want to add now i want to add it's just silly |
You don't need 10 callbacks. I think I'm using the generics as they are being used in the definitions of TypeScript for arrays: interface Array<T> {
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
} And with the proposed syntax example, if you want to add colour to all of them you could just do: function addColour<T extends {}>(primitive: T then primitiveWithColour: T & {colour: string}, colour: string) {
primitiveWithColour.colour = colour;
} Generics are totally compatible with that, but they are just not mandatory. |
this feature has to be build around generics, generics are the main use case, the main use case calls for prime time support from the language, using non-generics would be a special case 10 callbacks are necessary if you don't want to deal with |
Why it has to be built around generics? They can be used, but I don't see the need for mandatory usage of generics if you know exactly the type before and the type after the change. |
i didn't say generics are mandatory, all i said that generics are the main use case while specific types are a special case, and the reason i brought it up is that the syntax should rather be favoring the main case, not the special case |
This doesn't come up often enough to justify the investment necessary to create this behavior |
Currently there are 2 ways to enforce implementing an interface over an object:
In both cases we deal with a newly created object, however there are also situations when an already created object needs to be extended to comply to some more elaborate interface.
A good example are mixins and scenarios alike:
I am not aware how to make the extend function type safe, other than copying each field from
doThis
to the resulting object. Which might or might not be a solution (functions are harder to extend this way). If only TypeScript had a way to enforce an interface on the given object it would be very helpful.The text was updated successfully, but these errors were encountered: