One thing I love about typescript is they care more for the actual JS/library ecosystem as-is, and don't design a language for an ecosystem that should-be.
On that note, our new Redux Toolkit package is written in TS, and designed to work great in TS apps with a minimal amount of type declarations needed. Really just declare the type of the action in the reducer, and get everything else for free. The new React-Redux hooks API is also a lot easier to use with TS as well.
I showed how to use both of those in the Redux Toolkit "Advanced Tutorial" docs page:
https://redux-toolkit.js.org/tutorials/advanced-tutorial
On a related note, I also recently put up a long blog post detailing my own journey learning and using TS, as both a lib maintainer and an app developer, with my takeaways on the pros and cons of using TS:
https://blog.isquaredsoftware.com/2019/11/blogged-answers-le...
The value comes when you consider what happens if a reducer (or saga or whatever) is updated in a breaking way (or removed wholesale).
Example: consider a scenario where you use `connected-react-router`, your codebase fills up with history actions being dispatched, then one day you remove `connected-react-router`, or its major version is updated and introduces some breaking change.
If you have a union type that includes all your own actions plus the `LocationChangeAction` from connected-react-router (and you use is consistently - e.g. your components take `Dispatch<YourAction>`) then you'll learn about the breaking change when your app fails to compile.
The alternative is you catch it later than compile-time, at worst _much_ later.
You might decide that the overhead in this scenario isn't worth it, and that's your call to make (I believe it is worth it) but hopefully this gives an idea of the point.
That being said, I still think TypeScript's solution to handling them of using "type guards" where the type of a variable changes in different scopes is definitely designed to match common JS patterns at the cost of added complexity. Most other languages I know of only have a single type for a variable (and if you want to pattern match you must give each case a new variable name).
This may be pedantic, but with subtyping many OO languages can give many different distinct types for a variable. In Java you can assign any non-primitive typed expression to a variable of type Object. So almost any expression in Java can be typed as Object.
What you are describing as novel is rather the phenomenon of type refinement in a pattern match or conditional expression. When an expression undergoes pattern matching, its type becomes increasingly refined. This is a useful feature in intermediate-to-advanced Haskell (known as GADT) as well as dependently typed languages.
Sure, but if you do this in Java you must use different variable names for the reference of type Animal and the reference of type Object.
I think algebraic data types and type refinement are great, but I’m not a fan of automagically changing the types of variables in different scopes if it can’t be applied consistently.
FWIW, Go also changes the variable's type in type-switch cases: https://tour.golang.org/methods/16
Personally it doesn't feel complicated to use/understand (at least the kind of thing that these languages do)
I'm still kinda new to the idea of using conditional branches to inform static type checkers but it sounds like it's an idea that has been thought about in some depth [1][2][3] (i.e. doesn't sound like it was jimmy-rigged to match common JS patterns).
I personally love type refinement. Hacklang's type refinement was the first time it clicked that statically typed languages could _actually help_ you write code instead of get in the way.
[0] https://docs.hhvm.com/hack/types/type-refinement
[1] https://sites.cs.ucsb.edu/~benh/research/papers/kashyap13typ...
[2] Type refinements (page 23) https://drops.dagstuhl.de/opus/volltexte/2018/9219/pdf/LIPIc...
[3] Occurrence Typing (section 8.5) http://soft.vub.ac.be/Publications/2019/vub-soft-phd-19-02.p...
Your server side code should have runtime validation though. You can't and shouldn't rely on TypeScript there for security reasons.
Client (JSON) messages will pretty much always start out as any, and you can't assume they fit your union any way shape or form. Taking the Cat|Dog example from the article, a client may pass {"kind":"cat", "bark": "woof"} and if you just go blindly from there to the union and then try to narrow that down, you've got yourself a problem.
You should use a library like JOI (or type guards with custom validation) to first make sure it fits your union type, then you can go wild with discriminated unions.
I always wanted a library that could perform runtime validation given a typescript type, but so far typescript doesn't expose enough type information in decorators for this.
Not really, unless you define any as union of every possible type. Also TypeScript won't realize you have narrowed the type (because it doesn't use such a definition) with all your checking unless you cheat with type guards (which are really just a prettier cast with optional user-created validation).
Then there's also the fact that thanks to getters your narrowing from any may be incorrect, which is probably why you can only narrow to basic types or with an instanceof operator, neither of which will help you with your JSON input.
tl;dr: Discriminated unions won't help you when you're starting out from any, because any is not an union.
I suppose I'm saying that the validation function itself is the precursor to discriminated unions being useful. <any> run through a type predicate function or an assertion type function (https://www.typescriptlang.org/docs/handbook/release-notes/t...) produces a typed object that conforms to your requirements for the type.
I definitely do wish there were some way to create these assertions/validators from the types automatically, but I think that might be impossible.
Apologies for any formatting issues, on mobile:
``` foo:any; isAnimal(foo); if(isDog(foo)){ // Dog } else { // Cat } }
function isAnimal(thing: any): asserts thing is Animal { if (thing.kind !== "dog" && thing.kind !== "cat && etc){ throw new AssertionError("Not an animal!"); } ```
// ...etc, except that string literals probably exist in an Array or similar, ie types that can be progressively derived / enhanced from the runtime code.