7 ms·
Nit: they're not asking for runtime type safety; they're asking for the ability to reflect on types (at compile time, to generate values) so that they can use t
by tylerhou 3y ago
Nit: they're not asking for runtime type safety; they're asking for the ability to reflect on types (at compile time, to generate values) so that they can use type information at runtime.
This helps ensure runtime type safety because (for example) it would be great to have a generic "validation" function that takes an arbitrary interface and an arbitrary object and validates that object. One way to implement this would be to use /compile time/ reflection to generate code (TS code hypothetical, because I write C++ nowadays):
function validate<T>(obj: Any): T | null {
switch constexpr (T) {
case String:
return typeof obj == "string" ? obj : null;
case Array<U>
if (!Array.isArray(obj)) {
return null;
}
for (const u of obj) {
if (validate<U>(u) == null) {
return null;
}
}
return obj;
// ... more base cases
}
for (const prop: (keyof T) of Reflect<T>.Properties()) {
if (validate<T[prop]>(obj[prop]) == null) {
return null;
}
}
return obj;
}
interface Date {
year: String;
month: String;
day: String;
}
It would be great if this could generate /JavaScript/ code:
function validate__String__(obj) {
return typeof obj == "string" ? obj : null;
}
function validate__Array$Date$__(obj) {
if (!Array.isArray(obj)) {
return null;
}
for (const u of obj) {
if (validate__Date__(u) == null) {
return null;
}
}
return obj;
}
function validate__Date__(obj) {
for (const prop of ["year", "month", "day"])) {
if (validate__String__(obj[prop]) == null) {
return null;
}
}
return obj;
}
Unfortunately this is not possible (AFAIK) in TypeScript currently, and will not be possible with TypeScript's current philosophy.
(The above example is a hypothetical TypeScript compiler that might support "templated" generic functions; with just RTTI TypeScript could accomplish the same thing in a non-templated function by passing in `T` as a function parameter at runtime and doing runtime comparisons on `T`.)
- deleted 3y ago[deleted]
- golergka 3y agoI just declare these structures with io-ts, and get validation, serialization, deserialization code and types for free.
- JoBrad 3y agoThat library is mentioned in the article
- TheRealPomax 3y agoHere's the thing though: if you wrote your TS properly, you don't need this and asking for it just highlights that you're not using TS the way it's meant to be used. The only place you need runtime type enforcement (when you're writing your own code in TS) is for validating third party data at the point where you're ingesting it into your own code. Once it's in there, it is type safe if you used TS to compile your code to JS, because your function calls and data copies and everything else that moves data around was defined in terms of compatible shapes. And we already have runtime validation libraries to cover that validation step. So many of them.
- still_grokking 3y ago> Once it's in there, it is type safe if you used TS to compile your code to JS […] To the extend that TS's type-system is unsound, and everything may crash at runtime at random anytime.
- ncallaway 3y ago> And we already have runtime validation libraries to cover that validation step. Right, but now my type information for inbound data must be in two places: the typescript type, and the validation schema. And heaven forbid I make a mistake and those two become out of sync. Yes, I can use something like zod to define a schema then infer the type from it, but those inferred types are often … suboptimal to work with.
- rizky05 3y agoZod is great, why do you think it is suboptimal ?
- ncallaway 3y agoI like a lot of things about zod (it's what I use when I need to do this kind of validation), but when I was working with a mildly complicated schema the type that z.infer<Schema> produced wasn't great for me. When I produce a type definition for a nested schema, I'll produce types for individual pieces. So type Schema = { foo: Foo; bar: Bar; baz: Baz[]; } type Foo = { red: Color; white: Color; blue: Color; } type Color = { r: number; g: number; b: number; } type Bar = {...} type Baz = {...} I couldn't find a clean way to decompose the type that z.infer<Schema> produced. It would give me a single type object that was deeply structured. For a pretty flat-simple small schema, z.infer<> was totally fine. For the more complicated schema it wasn't _terrible_, I still used it. I made it work, but it definitely wasn't the dev experience I was hoping for. I think something that went the other way would be much preferable for me. I'd rather define the typescript type, then have some validate<Schema>() function that was available to me. Basically, I think it's _easier_ to get a generated validate() function to play nicely with the rest of my code, than it is to get the inferred type to play nicely with the rest of my code.
- paxys 3y agoI still don't see the point. There is a long list of libraries that do exactly this, and do it well enough. The author has linked to them himself. The overall benefit of this would be maybe slightly better syntax for these libraries (even that is doubtful, because plenty of them already have a `reflect<T>()` interface), but still zero runtime benefit. And getting it to be accurate for 100% of cases would entail exactly what I mentioned – writing an entire runtime to do this inference.
- Nullabillity 3y agoSo now you're stuck writing for an awkward inner platform language rather than using the TS syntax itself.
- paxys 3y agoWhat's this "awkward inner platform language"? They are asking for a `typescript.generateRuntimeType<T>()` function to be native to the language. Well plenty of libraries provide exactly this syntax today. Here's tst-reflect: `const type = getType<T>()`. Notice any difference?
- Nullabillity 3y agoBut tst-reflect seems to work by effectively building their own forked tsc compiler.
- paxys 3y agoNo, they have a plugin into the typescript compiler which uses their API. tsc already exposes all of the info they need.
- Dylan16807 3y agoOkay so it's not a forked compiler but it's adding a feature to the compiler. At which point it makes a lot of sense to ask why that feature shouldn't be merged.
- andix 3y agoIt would be great if McDonalds would serve first class sushi, and pizza, and seafood, and pasta, and steak, and if they would deliver frozen meals, and iron your laundry, and ... They just serve burgers and fries. And they are doing very well with that strategy. Same goes for TypeScript.
- RyanCavanaugh 3y agoNot to be flip, but if it were really all this easy, we would have done it already. There are dozens of questions you can throw at this code: What if the input's a union? What if it's a nested union -- how do you avoid combinatorial explosion? What if the input is a function -- how do you validate its parameter types using runtime information? What if the input is a conditional type? What if you're inside a generic function? The list is enormous and it quickly gets into "you've dug too deep and unleashed a Balrog" territory once you get beyond the primitives.
- twosdai 3y agoCan we just start with primitives and see what happens.
- bastawhiz 3y agoJavaScript already has this. It's called typeof.
- galaxyLogic 3y agoAnd instanceof
- jupp0r 3y agoTypescript purposefully influences this not at all.
- pas 3y agoMaybe I'm very mistaken, but to me it seems this code snippet is basically an alternative to writing a ton of "infer"-s and overloads, no? So the same pattern matching could be used in the "switch". Whatever the complier knows can be locally matched, and the combinations have to be already handled by the developer.
- jitl 3y agoIt's hard to come up with a compiler that produces a sound checker for arbitrarily complex union/intersection types. Perhaps there could be a restriction on reflection to "simple enough" types, but that's always going to be a weirdly moving target based on heuristics. There's already cases where Typescript tries to generate ~40MB+ .d.ts files which are just re-stating the types themselves. So it's easy to imagine a validator compiler emitting 100MB+ of code to check more wild and crazy types.
- scotty79 3y agoIf that's so much needed I wonder why there isn't a thriving ecosystem of pluggable typescript preprocessors that add whatever values based on types direcly to typescript source before compilation.
- andix 3y agoThere are a lot, and some are mentioned here in the comments.