-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Subtraction types #4183
Comments
What about something like this? interface DoNotCalculateAgain {
getBoundingClientRect(): void;
}
let y: DoNotCalculateAgain & HTMLElement;
// z: void, so an inevitable compile error on use
let z = y.getBoundingClientRect(); |
hm, didn't know it works this way, could be useful for anything that doesn't return |
Alternatively, wouldn't you have the object type itself guard against this type of re-initialization? ie class MyElement {
private boundResult = ...
public getBoundingClientRect() {
if(boundResult) return boundResult
...
boundResult = ...
return ...
}
}``` |
Another example: to support the principle of interface segregation, instead of passing a thick interface to a specialized function that only needs a few properties we could have cut it to a sub-type that would be just sufficient enough to run leaving all irrelevant members outside (same can be done today by employing existing features, however at a price of larger code base and requiring more maintenance) |
@Aleksey-Bykov , @RyanCavanaugh. |
open, needs a proposal, considered as a suggestion |
Please see https://github.com/Microsoft/TypeScript/wiki/FAQ#what-do-the-labels-on-these-issues-mean for label description. |
@mhegazy, sorry, thank you! |
It seems like very useful functionality. |
I think there should be more set operations over the fields, like subtraction there could be intersection of fields: interface A {
firstName: string;
lastName: string;
}
interface B {
firstName: string;
grade: string;
}
// "Set operation of intersection of fields"
let c: typeof A fieldIntersection B
// would mean c is now
interface C {
firstName: string;
} Not to be confused with intersection types which are actually field union. There are use cases for field subtraction and field intersection in a LINQ like type-safe SQL builders. |
numbers without NaN would be another interesting case for subtraction types: |
@Aleksey-Bykov Aren't |
I'd like this feature, but its implementation might be pretty tricky. Specifically I'd like it to make it easier to write fluent APIs that prevent repeated calls, similar to the original example when loading data. It seems like a possible way to implement this might be to create a NOT type
With unions: function guard(val: A | ~A | B) {
if(isA(val)) {
// val: A
} else {
// val: ~A | B
// exposes properties in B that aren't in A
if (isB(val)) {
// val: B this is probably OK if B has properties that are present in A, because B is after ~A in the type definition
} else {
// val: ~A
}
} With intersections: A question is whether For simplicity it may make sense to not treat them as distinct types. In either case it looks like we can run into trouble with type guards: function impossibleGuard(val: A & B | ~A) {
// val exposes properties in B but not A
if(!notA(val)) {
// val: A & B instead of just B, but val shouldn't actually have any properties that are in A
}
function impossibleGuard2(val: B | ~A) {
// val exposes properties in B but not A
if(isB(val)) {
// val: B, but it can't actually have defined properties that are in A
}
} The major issue here really seems to be that order matters now when NOT types are in play. |
might be related #7993 |
@masaeedu I'm interested in having a clean, comprehensible and universal algebra of types. It's not the business of a type operator to exclude things like |
It's a question of convenience. When you say A clean and comprehensible algebra of types is a goal I share, but there's nothing inherently unclean or opaque about logical disjunction. |
Having them become nullable is probably fine -- real-life use-cases likely all do something like |
Yes. I want |
@tycho01 In situations where you're intersecting the negated type with a non-nullable type, both If I have a |
@masaeedu: I thought in |
@tycho01 In |
@marsiancba I think Minus mapped type can be useful, thanks for the idea. export type Minus<T, U> = {[P in Diff<keyof T, keyof U>]: T[P]};
Minus<{ a: string, b?: number, c: boolean }, { a: any }>; // { b: number | undefined; c: boolean; }
export type Minus2<T, U> = Pick<T, Diff<keyof T, keyof U>>;
Minus2<{ a: string, b?: number, c: boolean }, { a: any }>; // { b?: number | undefined; c: boolean; } |
With #21847 type C15 = Exclude<number | string, string>; // number
type C16 = Exclude<"a" | "b" | "c", "b">; // "a" | "c" Another type added in #21847 is type C17 = NonNullable<number | undefined | null>; // number |
Awesome news! Can't wait to play around with this. |
closing since the major part of the issue is covered by conditional types, the rest is too vague and mostly irrelevant |
I plead for a reopening of this issue.@Aleksey-Bykov , you may have seen my comment at your #22375 ... I'm unable to have my decorator accept distinct signatures from static to instance side. #21847 seem the fix but event my TS v2.7.2 released 21 days back says it cannot find |
@SalathielGenese This is shipping as part of 2.8. https://github.com/Microsoft/TypeScript/wiki/Roadmap npm install typescript@next |
Much thanks @bcherny [UPDATE]I've just moved to Seem like there no way by which I can tell TS that an object ( |
I'm still playing around with TypeScript 2.8, but FYI, this is included as part of the new "Conditional Types" feature, documented here: This note from that page seems worth highlighting:
|
Exclude<string, 'foo'> is just |
@tycho01 sure, you can pull tricks to kind of sort of fake it in certain circumstances, but even that doesn't fully work: type NotFoo<X extends string> = X extends 'foo' ? never : X;
declare function handleNotFoo<T extends string & NotFoo<U>, U extends string = T>(val: T): void;
handleNotFoo('foo'); // correctly forbidden
handleNotFoo('foo' as string); // oops, that was allowed |
I don't understand how However, for someone who always seems to be learning about new type features in TypeScript, it totally amazes me it can even be made to work normally: |
it doesn't have to be forced though with the same effect function id<T>(value: T): T { return value; }
handleNotFoo(id<string>('foo')); or const foo = 'foo';
let x = foo;
handleNotFoo(x); |
|
|
To forbid
If you wanna generalize to automate that
On auto-widening being evil, #17785. |
Another type-safety measure. Sometimes it's desired to limit what developers can do with a value. Not allowing them to get to certain properties of it looks sufficient.
Example: We render HTML elements to PDF on the client side. In order to do so we need to run
element.getBoundingClientRect
to get a bounding box in effect. It is an expensive operation that we wish could only be done once and then the result of it would be passed around along with the element to be rendered. Unfortunately nothing stops developers from ignoring that result and running the same method again and again as long as they can get toelement.getBoundingClientRect
. Now I wish I could strip that method so no-one can see it once the box is calculated. Subtaction types would solve the problem.Proposal
add a type operator that produces a new type out of 2 given types according to the rules that follow:
This feature would require a new negated type like
number ~ string
which is anumber
that cannot takenumber & string
.As far as the precedence of new type operator, it should go:
&
|
-
so that
number & boolean | string - string
, means((number & boolean) | string) - string
Generics
Primitives
-
operation should result to an type errornever
andany
should be specially handledProducts
-
operation that produces the type of the resulting property of the same name-
on 2 properties of the same name gives{}
, the property gets dropped from the resulting typeFunctions (2 certain signatures)
-
operator-
on 2 parameters gives{}
the resulting parameter is{}
{}
the resulting type is{}
Overloads
The text was updated successfully, but these errors were encountered: