-
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
Type merging improvement => x & void = x since y & never = never, instead of u & void = illogical constraint #24852
Comments
Hi, WesleyOlis! I can tell you that you need to update your docs ;-)
Roughly speaking, Using With that said I think there is a bug we could open here, which is that |
I don't understand what problem the OP solves. Some motivating scenarios would be really useful.
I just have to correct this wherever I see it - a const f: () => void = () => 42; // Legal
console.log(f() === undefined); // False |
Oh, that's interesting and I definitely did not realize that. I would love if at some point we could publish a sort of formal semantics for this thing, as I would not have expected something like " |
Well if I stick to the topic! There are three perspectives(Dimensions) of analysis when dealing with types, one is based on looking at things from the perspective of sets and what they can represent; the other is from their implicit compound operator function that is perform when resolving types, with an intersections and unions when used in conjunction with a type; and type assignment A, Type assignment, which types in the type set of identifies in B can be assigned to one another.Each type indentifies, defines for each of the following, which types can be assigned to it.
B, Sets perspective:If we look at types that can be used in a set then I see the following set of types being used, were void and any are special
C, Compound Operator perspective, when encounter with an intersected or union in type definition.
any, improvement for autosuggestion, auto complete with typingsThe AST Backing data structure for any, should have a field, that contains an AST of the types it consumes as an operator, should preserve the relationships. This will allow autosuggest and complete, to interrogate and any data structure and provide more comprehensive types and structure as suggestions prefix with a *{a:number, b: string} | *number | *SomeInterface as as apposed to nothing. From operator perspective, void and never do the opersite things.
With void and never defined with these augmented operator resolution properties for type resolution and merging. On can very easily now achieve most patterns with sets, unions, intersections, when doing recursive type processing. never, void are consider operators, when used with union and intersection as they modify the behaviours of union and intersection, when encounter with a non-operator type set. The best is when you are required to seed any problem, then you seed it with void, when void is intersected with or union with another type it nukes(morphs) it self, leaving just the type is was merged with. Example, programming with a type system veneer layer, to generate type definitions..This is really just the basic idea..
Here is a whole bunch hacking around with types and other attempts, before we found very simple way, that can be simplified every more if void, is given operator capabilities when operated on by intersection and unions, when resolving types and merging types. https://github.com/wesleyolis/tsTypesFromASchemaImplemention You probably now, mabye get a bit more understanding for the context for were we were going with the following: I can say that our new approach to extracting types by programming in the type system veneer layer with types, is work out reasonable well for us. Our simple Joi Superset Extension framework as we flesh and resolve issues as we build it out, gives us primitive and structured typescript types, from a single simple joi schema instance implementation.. No pre-processors or generators needed.. :-) All we required to do.
|
I think this proposal is basically that the set operations of TypeScript should form two monoids: Type union is already a monoid with Type intersection is a semigroup with I think the latter is weakly defensible on mathematical grounds, now that I know that With that modification to |
The have one other reason, that void should have morphing behaviour like in the return statement indicated above would be for type checking reasons. Currently the Any compound operator, consumes all types and basically results in typing any further with any pointless!! As Any represents a fullset of possible type assignments, which means anything can happen. Basically is better to use @ts-ignore than an Any type. [A // @ts-ignore comment suppresses all errors that originate on the following line. It is recommended practice to have the remainder of the comment following @ts-ignore explain which error is being suppressed.] If the Void Compound operator, morphs into the first type is consumes(Basically what current happens for return type function statement assignment), then the type checking engine will still be able to perform type checking downstream, which will results in more of the program being being type checked. I like the idea that by upgrading void, instead changing the behaviour of any, provides a complete set or all possibilities of compound operator behaviour with their reciprocals (void/any), were never is really just their for compound operator behaviour. That or we have 3 type key words here, were only two of them are required. I am now starting to consider, what would be lost if Any definition is changed, to be the void behaviour I describe, as well as should never and void not be the same thing?? In the bigger picture of existing code and backwards compatibility what do we end up breaking and improving then... Because currently for void Compound operator results in illogical constraint which can't meet piratically, so we wouldn't be breaking anything existing. |
One advantage of Any type have this compound operator behavior of morphing, is that it would allow This is probably not a bad thing to work against: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3 |
Ran into a couple of other issues over past couple days and weekend, Sent me round in couple circles, reporting couple more non-bugs, then also then vetted a couple other things with my colleague in this area were I was wrong, but subtitle improvement can be made why went down that rabbit hole, with what I was wanting. To nights feature request and example, to clear that misconception up. We have also been debating or opposing interpretation of Any, were it should be morphing or not. All of this has lead me to have the following understanding of the Any type, which hopefully should help in shorting out why, void should morph and any can't and should never morph. Any, means we are not prepared to make an assumption about the type, we not for any reasons, prepared to further constrain the type. We can only know the constrained type for absolute certainty after an Any type has been validated at run time to be of a certain type form against which, we were validating, it can then after validation be assumed the type against which we have validated. If you reading a file from disk, you are assuming it has a certain type and structure, until its has been validate to have that type, form or structure, one is making an assumption. If you wrote the file and read it again, with out validating it, you are making the assumption it is of certain form and are hoping it has been tampered with in between. Everything that is not compiled in the same compiler instance, can have the potentially to be an assumption of the type you are making. any 3 party, could have cast the type. Any internal cast also mean you making an educated guess and assumption. Therefore, it is with this enlightenment that Any must always consume, nuke other types, when acting as an operator with Union or Intersection, because an assumption plus anything concrete, is still yet just that an assumption, with a shadow, probability of being right. The type checking engine, internally, could potentially softly attempt to narrow the AnyType, with a shadow type, to provide higher level of type checking probability for any usage internally, and emit helpfull soft warnings, but still must have back off, that were narrowing has happen, can become AnyType again, if it was wrong up till that point. Think creating a new context environment for compiling that block of code, were AnyType has been narrow, if wrong, emits warning, and changes the type back to an AnyType and then re-compiles that section with reverted context of AnyType, as an example. |
You are overthinking this. So for example if |
I hope this gives you some context on the bigger picture that we are looking at, were we current see things are going. |
Here are a couple more reason and examples of the patterns that can be formed, which now having problems with, when it comes to extends and structural typing, in which having a seed void morphing operator, would definitely simplify things as a result in perfectly recursive and simplified extraction methods, with less exceptions, because one can't initialization a property in an interface with any or void, right now, as pattern nukes all recursive intersection. Typically we are mainly targeting methods and not properties, which means interface InterA
{
MethodA() : this & {someTypeMinipulations};
MethodB() : void;
}
interface InterB
{
MethodA() : someCastingMinipulation;
MethodB() : void;
}
interface InterC
{
MethodA() : void;
MethodB() : void;
}
type Structure =
{
[index: string] : InterA | InterB | InterC
} Basically the extract method which uses the extends key word, is absolutely pointless here, type Extract<T extends Structure> =
{
[K in keyof T] :
T[K] extends InterA ? number :
T[K] extends InterB ? boolean :
T[K] extends InterB ? string :
'Mistake' & T[K]
} void as a type that can morph, which then also allows, guaranteed perfectly recursive methods to be written with out having to worry about the default initialised cases of the member variables of having to check were this class is of a certain type. interface InterA
{
MethodA() : void;
MethodB() : void;
commonKind : 'InterA'
}
interface InterB
{
MethodA() : void;
MethodB() : void;
commonKind : 'InterB'
}
interface InterC
{
MethodA() : void;
MethodB() : void;
commonKind : 'InterC'
} At the moment we really on the fact that a method must be called, to assign the first type to __interA. interface InterA
{
MethodA() : void;
MethodB() : void;
__interA : void
}
interface InterB
{
MethodA() : void;
MethodB() : void;
__interB : void
}
interface InterC
{
MethodA() : void;
MethodB() : void;
__interC : void
}
type Structure =
{
[index: string] : InterA | InterB | InterC
}
// Both are now equivalent.
type Extract<T extends Structure> =
{
[K in keyof T] :
T[K] extends InterA ? number :
T[K] extends InterB ? boolean :
T[K] extends InterB ? string :
'Mistake' & T[K]
}
type Extract<T extends Structure> =
{
[K in keyof T] :
T[K] extends { __interA : any } ? number :
T[K] extends { __interB : any } ? boolean :
T[K] extends { __interC : any } ? string :
'Mistake' & T[K]
} However this way below more explicitly in communicating what asking, that a certain property existing, type EnhanceAmazonType<T> = {
[K in keyof T] : T[K] extends {AttributesValues: any, Value: string}
? RecordOptional<T[K]['AttributesValues'], string> : extends {} ?
EnhanceAmazonType<T[K]> : T[K]
} Target specific class interface name with a nominal type guarantee, that their be no conflict, in structurally writing an incorrect classes type for 3rd party modules, that one would like to recursively re-write definitions for. type EnhanceAmazonType<T> = {
[K in keyof T] : T[K] nominally extends ServerType
? RecordOptional<T[K]['AttributesValues'], string> : extends {} ?
EnhanceAmazonType<T{K} : T[K]
} |
this could be quite similar to unknown propsal. Mabye need to compare differences pros and cons. |
It has also come to light that any from a set perspectives, should NOT include null and undefined, because those keywords are special and used to differential lack key of presents (undefined) and uninitialise contents nulls state. Especially from a patterns perspective when programming with and Any (undefined, null, Any), to be backwards compatible, explicit keyword may need to be used. |
There's a lot of text here and even more confusion about what |
Hi Guys,
I would just like to challenge some of the types engine logic here for the greater good of patterns.
I would like to recommend a change to the logic of how intersections are resolved with regards to void and never, which I see as reciprocal operations.
I would like to suggest think of/working with sets theory, then the type keywords void and never have specially meaning. cumulative, associative, distributive laws come to mind from probability and stats.
This would improve build up of types with recursion, were a default initial condition can be supplied.
Similar to SQL tricks of "1 == 1" + $WereConditional
The following similar explanation I have offered for SQL language comes to mind that I have previously given. Extract
Current Typescript Behavior:
void type identifier
never type identifier
Suggested Behaviour, perceived improved behaviour
void type identifier
never type identifier
One may also want to introduce boundaries for type simplification/resolution such as intersections and unions,
which will communicate to the compiler, were it should no longer simplify void and never conditions.
Think of using some form of brackets []. This cases below are not practical, however, example of the pattens.
or
Looking forward to your feedback.
The text was updated successfully, but these errors were encountered: