7 ms·
Ironically, as someone who has experience w/ both fast-and-loose JS and with "properly" statically typed languages, my beef with typescript is precisely that it
by lhorie 6y ago
Ironically, as someone who has experience w/ both fast-and-loose JS and with "properly" statically typed languages, my beef with typescript is precisely that it allows people to be fast and loose, sometimes in ways that are not super obvious. For example:
type Thing = {
name: string,
};
function getThing(): Thing {
return JSON.parse('{"error": "invalid id"}')
}
const thing: Thing = getThing(); // lie to me!
console.log(thing.name);
This is a trivial example to illustrate the idea that it's relatively easy to make a type that cascades through a very large app but which is backed by a complete lie.
I definitely think that there's also something to be said about meta-programming yak shaving (e.g. people wasting hours trying to write a "function" that takes an enum and outputs another enum w/ a fixed prefix, when simply writing out the enums would've taken 30 seconds w/ VSCode's multiple cursor feature)
Personally, the value I see in TS is in cementing documentable facts about a system: e.g. such and such function takes such and such well-defined entity, dot not treat this `options` argument as a generic bucket for pass-through garbage.
- kipple 6y agoThis particular example is close to my heart: the definition files for things like JSON.parse() were written before the "unknown" type was introduced as an alternative to "any" And because this "any" is coming from an external definition file, it can't be caught by tsc's --noImplicitAny nor eslint's no-explicit-any Those two rules allow us to protect us from our own code, but not external definitions/libraries. Relevant GitHub issue here — https://github.com/microsoft/TypeScript/issues/26188 https://github.com/microsoft/TypeScript/issues/26188
- kipple 6y agoLooks like eslint is working on a new set of rules that would help with these currently-unprotected any cases: https://github.com/typescript-eslint/typescript-eslint/issues/791 https://github.com/typescript-eslint/typescript-eslint/issue... > Create a rule which uses type information to determine when you're inadvertently breaking type safety by using an any, potentially without knowing. > Often libraries (or even the typescript defs themselves) can have weak(/lazy) types which return any. If you're not careful, you can inadvertently introduce anys within your codebase, leading to bugs that aren't caught by the compiler.
- jehlakj 6y agoIf you have to rely on restrictively typed languages to produce maintainable code, I think that’s a different topic. Typescript isn’t perfect by any means but it does a good job keeping code based easier to work with in a larger team. Much better than vanilla JavaScript
- madeofpalk 6y agoThe "curse" of Typescript is that it's built on Javascript, so you can tell it to lie to you all the time. This is the problem with introducing Typescript to a larger group of Javascript developers who arent all on board (because of either experience or being stubborn) - you still need to establish good habits and patterns for how to write typescript. I'm working through the very lie you're showing now because the disclipline wasnt established from the start to avoid these anti-patterns. Typescript isnt the perfect magic bullet that will disappear all your problems - you still need to have good developers writing good code, but TS will make it easier.
- dnautics 6y agoIn the large this curse is true for everything. The difference between 8 u8s and an f64 is thin for all architectures I can think of.
- lhorie 6y ago> you still need to have good developers writing good code, but TS will make it easier Very true. Typescript is still immensely helpful in the sense that it will complain if you pass a Thing to a function expecting a Foo, regardless of the fact that Thing is meant to be a thing but is actually an error. But IMHO, by sticking to a decision to be lean in terms of transpiled JS, TS suffers greatly from cases like the one I showed. It would be great if the compiler enforced that these types of unsafe casts had to be explicitly specified (even if via some strictness flag), and even better if it could do a better job at refining types through runtime constructs like `if (typeof thing.name == 'string')` (it does do it to some limited extent, but not enough to cover many common scenarios, due to the curse of targeting Javascript, as you mentioned)
- WorldMaker 6y agoYou can easily write your own "type guard functions" when you think TS is missing an inference from a runtime check you would like. The syntax is simple enough, it's just a slightly different return type from boolean: function isThing(value: unknown): value is Thing { return typeof value === 'object' && typeof value.name === 'string' } The `unknown` type in general is still pretty new, but will be greatly helpful for cleaning up a lot of code that assumes a type or used to use `any` or `{}` because there wasn't a better type to use in that situation. The Typescript team is hesitant to change the last few cases of `any` in lib.d.ts to `unknown` directly (such as `JSON.parse`) because that would be a big compatibility breaking bug. There was a proposal for a flag to do that (treat `any` as `unknown` in lib.d.ts), but I'm not currently aware of whatever happened to that idea. I would love to see it or something like it happen.
- evmar 6y agoIt's worth noting that this (good) example is unrelated to unsoundness, too -- it's related to built-in APIs using 'any'. Within Google we include a "strict" d.ts in every compilation that shadows this API (and one other) with one that instead returns "unknown". We have talked about going through the whole TS API and removing all the 'any' in this manner for this reason.
- joshAg 6y agoThat's a great idea! What's the other built-in that you shadow?
- evmar 6y agoThe definition of `new Map`: https://www.typescriptlang.org/play?#code/FDDGHsDsGcBcAJrwLz0gUwO7wMrtgBQCUA3PPAPQW74A8ArpANaTiaQB8ANIgIYBm6MFDjwAtijRZ4AWV4AHYmUrU582r0gBPHpq3d4jaAPQBCYEA https://www.typescriptlang.org/play?#code/FDDGHsDsGcBcAJrwLz... (I still think this is actually due to bug in TypeScript, see https://github.com/microsoft/TypeScript/issues/31990 https://github.com/microsoft/TypeScript/issues/31990 .)
- damagednoob 6y agoThis is an argument against strongly typed languages, not Typescript. You could do a similar thing in Java and C#. I don't know of any language that can stop someone from being a sloppy coder.
- xxpor 6y agoJava would absolutely stop you from returning a JSON object when you're supposed to return a Thing. You could cast it but you'd still get a ClassCastException. Now I haven't written JS or TS, so maybe I'm not understanding what you're saying?
- damagednoob 6y agoJust like Generics in Java, Typescript only exists at compile time. Talking about what Typescript doesn't do at runtime is moot.
- lmm 6y agoTypescript permits you to write code that does implicit unsafe casts at runtime. Type-safe languages don't. Java Generics don't let you implicitly do unsafe casts[1]; erased generics mean the unsafe cast wouldn't necessarily fail visibly at runtime (though there are warnings and linters to alert you to that, and techniques to work around it), but it would enforce that it was explicit in the code (and you would then know to add extra test coverage etc.). [1] You can implicitly do unsafe casts using arrays, but arrays aren't really using generics, they're a special-case builtin.
- winrid 6y agoThe example above is equivalent to gson.toJSON(string, Thing.class) It'll just return an empty Thing right? No ClassCastException.
- qw3rty01 6y agoIt's actually the opposite. This would throw an exception in a strongly typed language. It would silently return the wrong thing in javascript because it's not strongly typed.
- joshAg 6y agoIf you're not using a bunch of generics, check out typescript-is [1]. It takes little work to get it setup, but it generates run time type checks for you. I understand why typescript decided to not add this functionality to the core of the language, but it's starting to feel like the largest missing piece of typescript is a built-in way to generate run-time type-checks for user-defined types from just the type definition. The happy medium we've found with that module is using the runtime type-check on anything "unsafe" to bless the result using typescript-is's equals functionality, but still allowing programmers to use casting with a comment justifying its necessity. For us our list of unsafe is results pulled from the db, anything parsed from JSON, and incoming request bodies (which can be a special case of parsing from JSON, but not always). [1]: https://github.com/woutervh-/typescript-is https://github.com/woutervh-/typescript-is
- Shacklz 6y agoDoing runtime-typechecks really is a blessing, and it would be great if typescript would provide this as a language-feature. There are two more libraries I know which are great for that: * Zod: https://github.com/colinhacks/zod https://github.com/colinhacks/zod * io-ts: https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts (for the more functional-programming-oriented)
- joshAg 6y agoWe decided against io-ts because we'd still have to write the validator ourself (unless we used their type system as the source of truth, which wouldn't easily work for our project because we generate most of our types from an openAPI schema), which is exactly what we wanted to avoid doing. I hadn't heard of Zod before, but i really liked the "parse don't validate" blog post linked. Turns out that's exactly how we've been using typescript-is. But it still suffers from the problem of expecting to be the source of truth and thus not working well with generated types.
- eropple 6y agoI like io-ts, but I've moved mostly to runtypes: https://github.com/pelotom/runtypes https://github.com/pelotom/runtypes I forget why Zod didn't pass muster for me now, but I've looked at it before and didn't find a compelling reason to jump to it. Thought it seemed good, though. I really wish at least one of these let you emit JSON schema back out, though. That'd be way easier for dealing with stuff like OpenAPI.
- prophesi 6y agoJust a quick tip that helped my team with such issues: - For every call to an API, or input from the user, there should be a few tests checking for unexpected data This rule alone forces us to fix the majority of runtime errors such as this example (since JSON.parse() is usually called when handling an API response)
- adverbly 6y agoYou're not wrong. That's a good example of why someone might have beef with typescript. But as others have been saying, tooling enabling faster dev is one of the main reasons people use typescript. Safety comes second for most folks. And the tooling works just fine in that example.
- ben509 6y ago> (e.g. people wasting hours trying to write a "function" that takes an enum and outputs another enum w/ a fixed prefix... Metaprogramming shouldn't suck so much that this takes hours.
- chadlavi 6y agoSure, but you also should never just blindly trust a JSON.parse, and if possible you should avoid typing functions for exactly this reason, and let their return types be implied instead. If you didn't type `getThing` then its return type would be `any` and you'd get an error in your IDE on the last line.
- uryga 6y agosorry but i feel like this is backwards. the error - incorrectly assuming an `any` will be a `Thing` - is in `getThing`, and that's where the error message should be. getting an error in code that calls `getThing` instead would just obscure the real issue. also, i think "avoid typing functions [...] and let their return types be implied" is terrible advice. argument and return types are the thing you should type explicitly, because it makes the errors local. otherwise you'll get all the fun of tracking down where that `null has no property "blah"` error came from, just at compile time
- akiselev 6y agoIf you don't annotate the return value you open yourself up to another class of mistakes because the compiler can't check that each branch returns the correct type, especially if type inference decides the function returns `any` which swallows `undefined` in type hints, while also losing the self-documenting aspect without an IDE.
- _fullpint 6y agoCheck out io-ts — it can take a minute if you’re not familiar with functional programming, it’s built on top of fp-ts. Being able to encode/decode IO safely has been a huge blessing when working with JSON.
- pattrn 6y agoThis is the one thing that bothers me about TypeScript as well. I've ended up using frameworks like `zod` to do runtime type checking of endpoints. It has gone a long way towards decreasing edge case failures -- I just wish they had something like this built into the language.
- gherkinnn 6y agoNot doing runtime checks is a deliberate decision: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals https://github.com/Microsoft/TypeScript/wiki/TypeScript-Desi... So Zod it is. I like it.
- Orphis 6y agoTo me, the solution is simple: use a single source (some IDL format) to define objects at API boundaries and have your frontend and backend use it. It will make it a lot harder to return wrong types from your calls. Personally, I like gRPC + Protobuf.
- kelnos 6y agoWait... so I assume `JSON.parse()` returns `any`, yeah? TS automatically downcasts (in function returns and assignment)? How is that at all useful?
- akiselev 6y agoIt's a historical artifact of Typescript's development. The `unknown` type wasn't introduced until Typescript 3.0 and since `JSON.parse` is part of the Javascript "standard library," changing it now would be a brutal breaking change across almost the entire ecosystem.
- presentation 6y agothis seems to be an issue with bad typings - ideally `JSON.parse()` would return `unknown` and then it wouldn't lie to you, but that would probably break a lot of people's code. EDIT: https://github.com/microsoft/TypeScript/issues/26188 https://github.com/microsoft/TypeScript/issues/26188
- diob 6y agoI've always told folks that no technology will save you from your own developers. A good application can be written in any technology, just like a bad application can be written in any technology. Certain technologies will try to guide you to a better place, but it's up to developers to take advantage of it. Or to do crazy stuff like the above. . .
- winrid 6y agoThe problem in your example isn't TypeScript. It's the JSON parser. If you used GSON in Java, you'd get the same result, despite being statically typed with runtime type checks. You can implement the JSON parser to be type safe. For example, this is how nest-dto can prevent this.