Closed zpdDG4gta8XKpMCd closed 2 years ago
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 doThis
position?
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 https://github.com/Microsoft/TypeScript/issues/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 should be sugar for object.assign, so no non-enumrable and only own properties. Not sure if this is what you had in mind.
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: say we have 20 interfaces each with 10 properties it's easy to manifest that a our class is going to implement all 20 of them
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 https://github.com/Microsoft/TypeScript/issues/8353?
I think it is different. Main difference is that rather than trying to track all possible execution branches that might reassign a variable (which makes it a very hard task) I am proposing a simpler task of enforcing a type change within the immediate scope of a dedicated function without accounting for anything might happen in a subroutine. On May 12, 2016 7:47 PM, "Mohamed Hegazy" notifications@github.com wrote:
so is this a different proposal for #8353 https://github.com/Microsoft/TypeScript/issues/8353?
— You are receiving this because you modified the open/close state. Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/8545#issuecomment-218917157
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
a
of type A
a
gets passed to a foo
functiona
must be of type B
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 a
to b
using casting, whereas in vanila javascript code this pattern is natural and very popular (which is one of your goals - support idiomatic javascript), so here i am
Looking at that example I see either #10421 or #22865
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 delete
sentence you couldn't represent the type of the obj without a cast.
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 (value: number) => number
or () => number
whereas here we have T => T
(observe no parameter list)
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 {phone: string} & { phone: number }
then that line will not pass TypeScript checks.
that's a very good question,
obj
is of type T extends { phone: string }
number
to string
back and forth as many times as needed (effectively being string | number
)string
prior to any return
statement or the end of an execution branch (which imply a return statement of void
)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 x2
by x
which to my knowledge goes against its goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals
5. Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
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 < T extends >
, T => T &
, and two types (the type before and the type after), mine adds then
and two identifiers (with their types). So basically any of them can be bigger or smaller depending on the size of the types and identifiers.
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 x then xAfter
or such.
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: =>
, ->
, ::
, >>
, <!>
, becomes
, etc
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 x
and y
properties and able to be manipulated by changing them
Before
and After
in your example are cute but what if i need the same addr
/ address
behavior applied to something else?
my point is that Before
should be rather a generic, because you have no idea upfront what it will really be
~your last example is not going to work without generics because T
and U
as declared (bare generics) have nothing to do with having { addr: }
or any other props~
so you are proposing to pass a callback for mutating bare T
and U
each time? in the bottom of my heart i like it a lot (it resonates with the category theory where you morph abstract objects not knowing anything about their nature), but this is not how generics are used in TS typically
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 x
, y
, and z
of type, say, number
so that you can work off that, which leads us to <T extends { x: number, y: number, x: number }>
besides say you have
interface Circle { x: number; y: number; r: number; }
interface Line { x1: number; x2: number, y1: number, y2: number; }
interface Box { ... }
interface Triangle { ... }
interface Star { ... }
interface NGon { ... }
now i want to add color
to all/any of them, according to you i need a special function for each single type of object
now i want to add label
to all/any of them, again i need a special function for each single type of object
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 extends
, i can give you an example but too lazy to write it, if extends
is not a problem then 10 callbacks are not required
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.