4 ms·
TypeScript has a crazy powerful type system because it has to be able to describe any crazy behavior that was implemented in JavaScript. I mean, just take a loo
by wrs 2y ago
TypeScript has a crazy powerful type system because it has to be able to describe any crazy behavior that was implemented in JavaScript. I mean, just take a look at @types/express-serve-static-core [0] or @types/lodash [1] to see the lengths TS will let you go.
If you write in TS to start with, you can use a more sane subset.
[0] https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-serve-static-core/index.d.ts https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...
[1] https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/common/common.d.ts https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...
- dhruvrajvanshi 2y agoRight. I'm aware. My point was that even though the type system is powerful, somehow, I'm able to represent everything I need to in Kotlin's type system just fine and it feels a lot more type safe because it will throw a type error at runtime in the right place if I do a bad cast. Typescript `as Foo` will not do anything at runtime, and it will just keep on going, then throw a type error somewhere else later (possibly across an async boundary). You can, in theory use very strong lint rules (disallow `as` operator in favour of Zod, disallow postfix ! operator), but no actual codebase that I've worked on has these checks. Even the ones with the strictest checks enabled have gaps. Not to mention, there's intentional unsoundness in the type system, so even if you wanted, you couldn't really create a save subset of TS. Then there's the issue of reading the library types of some generic heavy code. When I "go to definition" in my fastify codebase, I see stuff like this RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger> Which expands to this export type RouteHandlerMethod< RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>, RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger > = ( this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider, ContextConfig, Logger>, reply: FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfig, SchemaCompiler, TypeProvider> // This return type used to be a generic type argument. Due to TypeScript's inference of return types, this rendered returns unchecked. ) => ResolveFastifyReplyReturnType<TypeProvider, SchemaCompiler, RouteGeneric> Other languages somehow don't need types this complicated and they're still safer at runtime :shrug:
- rimunroe 2y ago> You can, in theory use very strong lint rules (disallow `as` operator in favour of Zod, disallow postfix ! operator), but no actual codebase that I've worked on has these checks. Even the ones with the strictest checks enabled have gaps. That's surprising. I've worked on a few codebases with reasonably mature TS usage and they've all disallowed as/!/any without an explicit comment to disable the rule and explain why the use is required there.
- wrs 2y agoIt's apples and oranges. The type system in Kotlin is integrated into the language. TypeScript is a preprocessor for JavaScript that isn't allowed to change the JavaScript language, or have any runtime. The "as Foo" construct is for you to tell TS that you know better than it, or that you deliberately want to bypass the type system. You can have a runtime check, but you have to write the code yourself (a type predicate), because TS doesn't write or change any JavaScript*, it just type-checks it. I've certainly worked in new codebases where a relatively simple subset of TS types were used. Even then there were a few places the crazy type system was helpful to have. For example, we enforced that the names of the properties in the global configuration object weren't allowed to have consecutive uppercase letters! (* with minor exceptions like transpiling for new JS features)
- everforward 2y agoI don't write Kotlin, but what that does (assuming I'm guessing at it correctly) requires far more awkward code in most other languages. That looks like it will allow you to extend the types of objects deep inside the library so that you could e.g. create your own Request object without having to type cast inside the HTTP handlers or wrap the entire library. That shifts the complexity of doing that out of the runtime and into the Typescript preprocessor where it's not going to mess with your production instances. I also don't think it's all that bad; it's a lot of generic types, but it doesn't appear to be doing anything particularly complicated. I do think they get awful, though. This is something I've been hacking on that I'm probably going to rewrite https://pastebin.com/VszX3MyE https://pastebin.com/VszX3MyE It's a wrapper around Electron's IPC and derives a type for the client from the type for the server (has to have the same methods and does some type finagling to strip out the server-specific types). It also dynamically generates a client based on the server prototype. The whole thing rapidly fell into the "neat but too complicated to be practical" hole.