Of course, that only works if we know the type statically just like in the example of the post. But that is exactly the usecase that is needed in this example and then the type-errors are gone.
Of course, that only works if we know the type statically just like in the example of the post. But that is exactly the usecase that is needed in this example and then the type-errors are gone.
https://github.com/Microsoft/TypeScript/wiki/TypeScript-Desi...
Edit:
Non-goals:
…
5. Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.Typescript only got official ECMAScript decorator support in the recent v5. ECMAScript decorators only got to stage 3 in April ‘22.
But decorator syntax is just a kind of syntax sugar over passing a function through another function, and you can do that today to achieve runtime type information (see zod etc). Zod could be rewritten using decorator syntax and still be “just JavaScript” while providing compile-time type support.
The distinction being that supporting ECMAScript features is a goal for Typescript, but they were perhaps too aggressive early on in investing in decorators and the Reflect Metadata API. They had the wisdom to put these behind “experimental” flags, but I think they got quite popular within the typescript community due to the early adoption of both Typescript and both those features by Angular, which was really the only major lib using TS for quite some time.
Yah, they've been out for ages. It's quite surprisingly how 1. long it's taken ECMA and 2. how quickly TypeScript took advantage of decorator syntax to improve TypeScript. I'd say it's a definite win for us people who love decorators.
> But decorator syntax is just a kind of syntax sugar over passing a function through another function, and you can do that today to achieve runtime type information
I'll have a look at Zod, thank you! I have to admit I like the simplicity of decorators; I'm playing with Dependency Injection and, while the loss of parameter injection is a bit disappointing, there are ways to work around it, e.g.
@Injectable([Dependency])
class Service {
constructor (private dependency: Dependency) { }
}
> [...] features by Angular, which was really the only major lib using TS for quite some time.Definitely. Although it'd be interesting to see how Angular handles the transition away from parameter injection; there's an open issue about it on their GitHub, but from what I can see none of the core members have spoken about it yet. <https://github.com/angular/angular/issues/50439>
The main proposal from a community member is to replace them with the Service Locator pattern (ew). Thankfully someone in-thread provided them with a little wisdom regarding why that's a terrible idea. Here's hoping Angular keeps a nice API.
Fetch returns “any” meaning you can’t trust the data you received is actually the data you expected. Bugs from this mismatch will be many lines away (on first use) and more difficult to find. Because of this “goal of the language” you cited, there’s no built-in way to validate any data at runtime. In nearly any other typed language I have some deserialization mechanism. Not so in Typescript!
This decision led to more bugs in our codebase than any other. The compiler actively lies to you about the types you’ll have at runtime. The only solutions are codegen or writing validators to poorly approximate what Typescript should give us for free.
Edit to add: I think “any” is almost always a big cop-out because you couldn’t be bothered figuring out the correct type, and it often causes problems (loss of type coverage) further down the line. I admit I do use “any” in my own code when I really need to, but a library should work harder to avoid it, and the standard platform typings should work harder still.
Types are inferred from the schema though personally I like to handwrite types as well to sense check that the schema describes the type I think it does
1. Types are not written in typescript anymore. Or you have to define them twice and manually ensure they match. ReturnType<typeof MyType> pollutes the codebase.
2. Types have to be defined in order, since they’re now consts. If you have a lot of types which embed other types, good luck determining that order by hand.
3. Recursive types need to be treated specially because a const variable can’t reference itself without some lazy evaluation mechanism.
TS could solve all of this by baking this into the language.
2. Use let and modify your types as new ones become available - union them with a new object that contains the new property you need
3. How often are you making recursive types?
I agree that all of this could be made easier, but zod is the best we have and great for most normal usage. The reason TS doesn't want to make this available at runtime is that it means so many changes they make will become breaking changes. Perhaps one day when there's less development on TS we'll see this get added
I really enjoyed using myzod (more performative, simple, zod) for awhile, but recently I’ve been using Typia, which is a codegen approach. I have mixed feelings about it, and from my own benchmarking it’s performance seems overstated, but the idea is sound: because we know the type, we can compile better, type-optimized serialize/deserialize functions.
As for not littering the codebase with runtime checks, it may be worth reiterating to the person above that you really should only do type determinations at the I/O edges: you parse your input, and it becomes known from then onwards. You runtime type-check your output, and its requirements propagate upwards through your program.
The ability to emit a parser/verifier would not require any other runtime or affect the speed of any other code.
I think it’s reasonable enough to allow other people to focus on runtime behavior. There’s still a lot to do to model js accurately.
In my personal opinion, the ideal ts would be one where you just write regular js, and the compiler is able to check all of it for correctness implicitly. That would require runtime validators etc to be explicitly written, yes, but you could “just write js” and the correctness of your program could be proven (with guidance to make it more provably correct when it is not yet).
This lets me run my inputs through a schema validator, and ensure that the type I'm using statically will match the schema used at runtime.
The correct type for values you don’t know the type of (like the response of an API call) is “unknown”.
TypeScript does not provide the facilities you describe because there is not a one-size-fits-all solution to the cases that are possible and common in JavaScript.
It is left to the developer to decide how to validate unknown data at the boundaries of the API.
There are third party libraries that facilitate this in different ways with different trade-offs.
The compiler actively lies to you about the types you’ll have at runtime.
I find this to be rare if you are using strict mode with proper TypeScript definition files for your platform and dependencies. Usually the lie is in your own code or bad dependencies when an “unknown” type (including “any”) is cast to a concrete type without being validated. In nearly any other typed language I have some deserialization mechanism.
Could you provide examples? I either don’t understand or I disagree.The lie is almost always in an external API response from fetch (hence the complaint about “any” above).
> Could you provide examples?
Off the top of my head… Go’s stdlib json.Unmarshal and Rust’s Serde derive Deserialize.
I was misunderstanding your point with the deserialize.
Edit: “using” -> “uses”
Yes, but one of those bad dependencies is the standard library.
- `JSON.parse` and `.json()` on response bodies both return `any`.
- `JSON.stringify`'s `replacer` argument is of type `(key: any, string: any) => any`.
- `PromiseRejectedResult["reason"]` is `any`.
There are certainly many others.
Regarding runtime type checking, if you were to write something that can handle the total space of possible TS types, you would end up with incredibly complex machinery. It would be hard to make it perform, both in terms of speed and bundle size, and it would be hard to predict. I think Zod or perhaps https://arktype.io/ which target a reasonable subset are the only way to go.
The downside is the underlying types tend to be more complex when viewed in your IDE, but I think it's worth it.
type Identity<T> = T
// This can be made recursive to an extent, alas I’m on mobile
type Merge<T> = {
[K in keyof T]: Identity<T[K]>
}
type ReadableFoo = Merge<UnreadableFoo>Predicates are the most performant but won't give you any indication on why it failed (ie. some nested field was null but was expected to be number etc).
Refutations is great sweet spot as it's fast while giving information about error.
Assertions are slow, but more often than not you don't care.
You can map between any of them, but it doesn't make much sense for mapping ie. assertion to predicate as you'd be paying cost for nested try/catch while dropping error information.
Refutation is great base for all 3.
Personally I'm fan of not introducing new language that runs at comp time, just use the same language to have macros and operations on types for free - just like Zig does it.
Typescript type system is already turing complete so it's not like they'd be loosing anything there.
I'm not sure if there is a) any "programming pattern" that can avoid this without other drawbacks and b) if there is any problem with emitting different code based on the types (at compiletime).
I suppose it could lead to breaking behaviour if the typesystem is changed, since it now can impact runtime code. Personally, I think this would be more than worth it, but maybe the typescript team has a different opinion or other reason.