It's encouraging seeing all the work going into making tagged unions a natural part of TS. I've tried to introduce them multiple times, and have been shot down by my team on every attempt. I think they still come across as "hacky," and more JS-minded people who are totally cool validating with if-else everywhere still can't appreciate the benefits of annotating data in this manner. I hope TS eventually arrives at a standard, low impact way to create and manipulate such structures; maybe more people will be on board then. It looks like we're moving in the right direction.
Well, tagged unions, like many TS features, are designed to enable type-checking on vaguely idiomatic JS code, aren't they? That checks properties of objects in if-else statements? It sucks that people aren't open to defining their structures in a way that aids type-checking, if that's what you mean!
Tagged unions still require rather cumbersome JavaScript syntax to use. While the type inference support is great, much of the benefit is IMHO lost because of the syntax.
The very, very related thing I found myself wishing I had just today is the ability to destructure discriminated union fields which only exist on one component type of the union.
That destructuring would be possible, with all the control flow analysis necessary to make it useful. Today, even that wouldn't be possible, so definitely excited for this change.
But, I'm curious if anyone knows if this change can extend to situations where a field isn't even defined in one of the unions, and the compiler can just insert an undefined type for those fields. Or, maybe this would have some unresolveable side-effects I'm not considering.
I don't think the language designers would consider this, because there is a difference in TypeScript between a property existing with type undefined and not existing, regardless of the fact that it might sometimes be convenient if a non-existent property acted like a property that existed and had type undefined.
One thing I'm wondering: with all the bells and whistles and insanely complex things TS now supports, why does it still fail to catch seemingly simple mistakes?
For instance, it's sooo easy to write a React component taking in another component as a prop and some props for that other component, to rendering it. But somehow lose the type safety of the props being passed in. Like, you write some generics and the compiler stops complaining inside your component. But if you go back out and then try to pass some weird props instead, it doesn't care. I feel it's too easy to write code that looks tyoe safe but subtly isn't.
1. Typescript has to work with Javascript, which inherently makes things difficult and you have to choose between correctness or ergonomics. TS went for the latter, which probably was a good decision, otherwise no one would use it now.
2. While I have a lot of respect for the creators of TS, the language is not very consistent and uses a lot of "special cases" or "special features" to solve specific problems. But it also means that it becomes hard to ensure that all these features work together well and in the way that users expect. Other languages lack features that TS offer, but they feel more "consistent" or reliable.
Because Typescript only exists on the compiler, while the browser only undestands JavaScript, it is the usual problem of guest languages, the platform leaks.
> it's sooo easy to write a React component taking in another component as a prop and some props for that other component ... but somehow lose the type safety of the props being passed in.
Previous comments interpreted this, I think, as "why don't I have type safety at runtime." Obviously, it's really little more than a very powerful static analyser on top of a superset of JavaScript. I'm not certain you were asking that; without a little more specifics to what you were trying to do or a code example, I'm not positive I'm understanding the issue you're describing correctly, but I think you're saying that in certain cases, with a React component where another React component is being passed as a parameter, along with other props, you can still reference the root component somewhere else with the wrong parameters and TypeScript doesn't complain.
Personally speaking, I have never written React without TypeScript in the last 3 years and have spent about half of my work time in that space. If you haven't taken a recent look and tried the scenario you describe in the latest version of the language, I'd suggest starting there. I remember, early on -- way prior to Hooks and the like -- we were looking at various tooling options around state management (react-redux/mobx/others) to use for a large project and ended up not choosing react-redux due to it being impossible/almost-impossible to get type safety in some cases[0].
Ninety-nine percent of the time when `tsc` fails to produce an error/indicate something useful to you in your IDE of choice like this, it's because implicit typing couldn't figure something important out and the project's configuration was set lax enough to quietly ignore it. One easy fix for me was to go full `strict`; disallow `any`. When typing can't be determined implicitly, you get a shiny error. This leads to the second thing that becomes necessary: providing types explicitly for functions/others -- in certain cases, sometimes because it doesn't work without it, sometimes just to have it clearly defined/obvious. Type aliases help a lot for drying things up and keeping the "type noise" to a minimum.
It's also possible that whatever tooling you're using to package/build things is interfering in some way (or publishing the result of the compile regardless of errors/warnings).
The strictest settings will bring a lot of new errors, and you'll probably be adding a lot of type info and adjusting code accordingly if you enable these in a project that was more lax, however, I've found it's usually worth it. The issues tend to also bring visibility to a really subtle bug or ... fifteen. Run the latest versions of TS -- improvements around this come constantly. And these days, support for TS on the library side is very solid[1].
Of course, it's still based on a dynamic language and has dynamic elements to it. It's always possible to violate the type system (heck, you can toss a comment in and violate it on a given line if you wish), but the problem you're describing I haven't remembered being a large source of grief, and most of the issues I used to run into from time to time aren't issues in recent versions.
If I misread your scenario, let me know; I've been pretty amazed at the kinds of things I can express with their type system and how well its compiler enforces those things[2] so I'm curious about where it really falls down.
[0] The specifics elude me, but I recall it had to do with the line that wrapped the component; this was three years ago and I had never re-examined the issue after that.
[1] Though I'd not encountered a component/library that I couldn't find a solid alternative to or workaround in some way to get it working in TS.
[2] It'd be nice if those extended in a manner that would handle things one encounters with Json data not quite matching up type-wise but a lot of that is because "it doesn't know how the object is going to be used/loaded at runtime" and you can't really expect it to do so.
I was a Flow holdout for the longest time. TypeScript's support for discriminated unions is one of the things which finally won me over. Super glad to see it being improved upon here.
Flow feels pretty dead. The VSCode extension is broken in major ways and hasn’t gotten a major update in years. The only mainstream editor that fully supports it is IntelliJ.
A record is a set of key-value pairs with the value having a given type (also called a struct, object, etc. depending on the context). In TypeScript, this is an example of a record:
const foo = { bar: 1, baz: "qux" };
Destructuring is an ergonomic feature where you can take the fields of a record and assign them to local variables. For example:
// Equivalent to
// const bar = foo.bar;
// const baz = foo.baz;
const { bar, baz } = foo;
Besides ergonomics, destructuring can offer features like exhaustiveness-checking, to make sure that you used all of the fields of a record.
A union type is a type which denotes that the value is one of two different types, indicated like this:
type Foo = number | string;
Sometimes, you have two different cases with the same type, and you want to distinguish between them. For example:
// Not helpful -- we can't tell from the value
// whether it's supposed to be a username or an email.
type UsernameOrEmail = string | string;
To handle this case, you can use a discriminated union instead. These unions have a special value which unambiguously indicates which case is present:
Thanks for the explanation. I've been programming for many years, and your examples make total sense to me, but reading sentences like "Control flow analysis for destructured discriminated unions" make me feel like a total impostor
The usefulness of this obviously depends on pattern matching control flow ops in Rust that don't exist in TypeScript but it would make my life just so much easier.
You can do a similar thing with discriminated unions in TS, with some caveats mainly around ergonomics. You can even get exhaustiveness checking with a little trickery
Edit, example of exhaustiveness checking:
switch(obj.kind) {
case "one": return ...
case "two": return ...
case "three": return ...
default:
// @ts-expect-error
throw Error("Didn't cover " + obj.kind)
}
This will actually err at compile time if you missed a case, because otherwise obj.kind will have type "never", which will cause a type error on that line, which will be expected due to the directive comment. If obj.kind is not "never", the code will not have an error, and so the directive will cause an error.
...it is definitely preferable having language-level support for this stuff though.
Actually, in practice I find TS union types much more useful than Rust enums, because you can mix and match options in different combinations freely. In Rust, you can't use one of the enum variants as a type on it's own, and I had to tediously create a lot of wrapper types that I didn't in TS.
Help me understand this feature. How does it differ from a discriminated union of object types? (I might be completely misunderstanding the feature you’re raising)
AFAICT this will have the most impact on folks using Typescript with Redux stores and similar dispatchers.I know I would have liked having this feature back when I was doing that work.
FWIW, our standard recommended Redux usage patterns for the last couple years have specifically _not_ needed switch statements to determine how to handle an action, because our official Redux Toolkit package has a `createSlice` API that lets you define case reducers as simple functions in an object. `createSlice` then generates all the action types internally, generates corresponding action creator functions, and handles calling the right case reducer when that action is dispatched.
Additionally, RTK has been designed to work great with TypeScript. Typically all you need to declare is the type of the state for that slice, and the payload for each reducer's action, and everything else is inferred.
{ type: 'SET_PERSON', payload: Person }
and
{ type: 'SET_AGE', payload: number }
Based on what type field equals to, TLS (Typescript Language Server - used by IDEs) and TSC (TypeScript Compiler) will know type of payload. It’s already possible to achieve that to some extent, but it’s really limited.
It's probably a simple bot that's hooked up to a GH app. Then just some custom code to react to specific types of events. Depending on what you need done, you'd possibly be able to replicate a lot of functionality within a GH action.
Seems like a good way of adding additional comment spam to your projects. What happened with having a GUI with audit logs that you can click a button/make a curl request to in order to trigger?
God, 19 out of 22 comments are all about CI and should not even be in the PR. How can people effectively work with so much noise in their PRs?
[+] [-] jim-jim-jim|4 years ago|reply
[+] [-] dgreensp|4 years ago|reply
[+] [-] goto11|4 years ago|reply
[+] [-] 015a|4 years ago|reply
The very, very related thing I found myself wishing I had just today is the ability to destructure discriminated union fields which only exist on one component type of the union.
As an example, with the following type system:
The ability to destructure like: Such that, at the time of destructuring: I believe, with this PR, if we re-structured the types above to: That destructuring would be possible, with all the control flow analysis necessary to make it useful. Today, even that wouldn't be possible, so definitely excited for this change.But, I'm curious if anyone knows if this change can extend to situations where a field isn't even defined in one of the unions, and the compiler can just insert an undefined type for those fields. Or, maybe this would have some unresolveable side-effects I'm not considering.
[+] [-] eyelidlessness|4 years ago|reply
- Optional in the union type
- Always present and has the correct type in the exists: true case
- Still optional in the exists: false case, but if you try to access it you always get a type error
[+] [-] dgreensp|4 years ago|reply
[+] [-] gherkinnn|4 years ago|reply
In short:
0 - https://www.typescriptlang.org/play?ts=4.6.0-dev.20211103#co...[+] [-] ljm|4 years ago|reply
What I see in OutputExists/OutputNotExists is actually a Maybe/Option type.
[+] [-] unknown|4 years ago|reply
[deleted]
[+] [-] matsemann|4 years ago|reply
For instance, it's sooo easy to write a React component taking in another component as a prop and some props for that other component, to rendering it. But somehow lose the type safety of the props being passed in. Like, you write some generics and the compiler stops complaining inside your component. But if you go back out and then try to pass some weird props instead, it doesn't care. I feel it's too easy to write code that looks tyoe safe but subtly isn't.
[+] [-] valenterry|4 years ago|reply
1. Typescript has to work with Javascript, which inherently makes things difficult and you have to choose between correctness or ergonomics. TS went for the latter, which probably was a good decision, otherwise no one would use it now.
2. While I have a lot of respect for the creators of TS, the language is not very consistent and uses a lot of "special cases" or "special features" to solve specific problems. But it also means that it becomes hard to ensure that all these features work together well and in the way that users expect. Other languages lack features that TS offer, but they feel more "consistent" or reliable.
[+] [-] pjmlp|4 years ago|reply
[+] [-] mdip|4 years ago|reply
Personally speaking, I have never written React without TypeScript in the last 3 years and have spent about half of my work time in that space. If you haven't taken a recent look and tried the scenario you describe in the latest version of the language, I'd suggest starting there. I remember, early on -- way prior to Hooks and the like -- we were looking at various tooling options around state management (react-redux/mobx/others) to use for a large project and ended up not choosing react-redux due to it being impossible/almost-impossible to get type safety in some cases[0].
Ninety-nine percent of the time when `tsc` fails to produce an error/indicate something useful to you in your IDE of choice like this, it's because implicit typing couldn't figure something important out and the project's configuration was set lax enough to quietly ignore it. One easy fix for me was to go full `strict`; disallow `any`. When typing can't be determined implicitly, you get a shiny error. This leads to the second thing that becomes necessary: providing types explicitly for functions/others -- in certain cases, sometimes because it doesn't work without it, sometimes just to have it clearly defined/obvious. Type aliases help a lot for drying things up and keeping the "type noise" to a minimum.
It's also possible that whatever tooling you're using to package/build things is interfering in some way (or publishing the result of the compile regardless of errors/warnings).
The strictest settings will bring a lot of new errors, and you'll probably be adding a lot of type info and adjusting code accordingly if you enable these in a project that was more lax, however, I've found it's usually worth it. The issues tend to also bring visibility to a really subtle bug or ... fifteen. Run the latest versions of TS -- improvements around this come constantly. And these days, support for TS on the library side is very solid[1].
Of course, it's still based on a dynamic language and has dynamic elements to it. It's always possible to violate the type system (heck, you can toss a comment in and violate it on a given line if you wish), but the problem you're describing I haven't remembered being a large source of grief, and most of the issues I used to run into from time to time aren't issues in recent versions.
If I misread your scenario, let me know; I've been pretty amazed at the kinds of things I can express with their type system and how well its compiler enforces those things[2] so I'm curious about where it really falls down.
[0] The specifics elude me, but I recall it had to do with the line that wrapped the component; this was three years ago and I had never re-examined the issue after that.
[1] Though I'd not encountered a component/library that I couldn't find a solid alternative to or workaround in some way to get it working in TS.
[2] It'd be nice if those extended in a manner that would handle things one encounters with Json data not quite matching up type-wise but a lot of that is because "it doesn't know how the object is going to be used/loaded at runtime" and you can't really expect it to do so.
[+] [-] bluepnume|4 years ago|reply
[+] [-] GeZe|4 years ago|reply
[0] https://flow.org/try/#0C4TwDgpgBAggxsAlgewHZQLwCgq6gHygG8oBr...
[+] [-] gherkinnn|4 years ago|reply
[+] [-] symlinkk|4 years ago|reply
[+] [-] arxanas|4 years ago|reply
A record is a set of key-value pairs with the value having a given type (also called a struct, object, etc. depending on the context). In TypeScript, this is an example of a record:
Destructuring is an ergonomic feature where you can take the fields of a record and assign them to local variables. For example: Besides ergonomics, destructuring can offer features like exhaustiveness-checking, to make sure that you used all of the fields of a record.A union type is a type which denotes that the value is one of two different types, indicated like this:
Sometimes, you have two different cases with the same type, and you want to distinguish between them. For example: To handle this case, you can use a discriminated union instead. These unions have a special value which unambiguously indicates which case is present: Sometimes, you have a discriminated union with different fields: Currently, TypeScript uses control-flow analysis to ensure that you're using the right kinds of fields depending on the discriminator value: However, you can't combine this feature with destructuring: With this pull request, you can do the above. This can be convenient with patterns like reducers (see https://github.com/microsoft/TypeScript/issues/46143 for an example).[+] [-] dekerta|4 years ago|reply
[+] [-] friedman23|4 years ago|reply
[+] [-] Zababa|4 years ago|reply
[+] [-] jb_s|4 years ago|reply
[+] [-] 19h|4 years ago|reply
The usefulness of this obviously depends on pattern matching control flow ops in Rust that don't exist in TypeScript but it would make my life just so much easier.
[+] [-] brundolf|4 years ago|reply
Edit, example of exhaustiveness checking:
This will actually err at compile time if you missed a case, because otherwise obj.kind will have type "never", which will cause a type error on that line, which will be expected due to the directive comment. If obj.kind is not "never", the code will not have an error, and so the directive will cause an error....it is definitely preferable having language-level support for this stuff though.
[+] [-] golergka|4 years ago|reply
Actually, in practice I find TS union types much more useful than Rust enums, because you can mix and match options in different combinations freely. In Rust, you can't use one of the enum variants as a type on it's own, and I had to tediously create a lot of wrapper types that I didn't in TS.
[+] [-] Waterluvian|4 years ago|reply
[+] [-] maxfurman|4 years ago|reply
AFAICT this will have the most impact on folks using Typescript with Redux stores and similar dispatchers.I know I would have liked having this feature back when I was doing that work.
[+] [-] acemarke|4 years ago|reply
Additionally, RTK has been designed to work great with TypeScript. Typically all you need to declare is the type of the state for that slice, and the payload for each reducer's action, and everything else is inferred.
See our Redux docs tutorials for details:
https://redux.js.org/tutorials/essentials/part-2-app-structu...
https://redux.js.org/tutorials/fundamentals/part-8-modern-re...
https://redux.js.org/tutorials/typescript-quick-start
[+] [-] tshaddox|4 years ago|reply
[+] [-] the_duke|4 years ago|reply
Like `if (action.kind === 'x')` / `switch (action.kind)`.
This "only" extends support to destructured discriminators.
Definitely nice to have, but of limited impact.
[+] [-] NaughtyShiba|4 years ago|reply
[+] [-] sizediterable|4 years ago|reply
[+] [-] aluminum96|4 years ago|reply
[+] [-] randomdata|4 years ago|reply
[+] [-] htunnicliff|4 years ago|reply
[+] [-] the_gipsy|4 years ago|reply
[+] [-] NaughtyShiba|4 years ago|reply
Simple example:
Lets say you can submit one of these payloads
{ type: 'SET_PERSON', payload: Person } and { type: 'SET_AGE', payload: number }
Based on what type field equals to, TLS (Typescript Language Server - used by IDEs) and TSC (TypeScript Compiler) will know type of payload. It’s already possible to achieve that to some extent, but it’s really limited.
[+] [-] sakesun|4 years ago|reply
@typescript-bot pack this @typescript-bot test this @typescript-bot user test this @typescript-bot run dt @typescript-bot perf test this
[+] [-] Master_Odin|4 years ago|reply
[+] [-] capableweb|4 years ago|reply
God, 19 out of 22 comments are all about CI and should not even be in the PR. How can people effectively work with so much noise in their PRs?
[+] [-] adamddev1|4 years ago|reply
[+] [-] revskill|4 years ago|reply