Why Become a TypeScript Fan?
betterprogramming.pub
betterprogramming.pub
If you ever have a program that outputs a lot of text but you can't redirect it to a file (maybe you need to interact with it)
Then type the command "script" and run whatever you need then type "exit" and you will be left behind with a file named "typescript" that contains whatever text was output in that terminal window.
See "info scriptreplay"
This might be unpopular opinion, but personally I'd love for JavaScript to be completely replaced with TS or something like it.
Not to worry! Google won't let that happen. They'll push for Dart instead.
But yes: TS can not fully capture what JS is capable of.
You might annoy the type checker, but if you're certain that you know better then there's always ways to disable those errors.
This only goes for some special cases and sadly react-redux with rematch is one of them. Typescript can not understand which types are present in my dispatch object, whereas it is possible with JS autocompletion!
Just wanted to bring up this rather curious exception to the rule.
That’s kind of dumb on the part of the IDE involved.
Unfortunately, no:
At least 50% of runtime issues I had earlier are now type errors at compilation and fixed quickly. I'll take boring any day, JS is not a language I want to spend more time with than necessary.
I just wish the compilation was faster.
Like most of us, I've been there myself in multiple occasions. It's not something I'm proud of and I'm grateful this behavior wears off when getting more experienced.
If you think 'this sucks because I don't understand this', chances are it doesn't really suck and you need to either learn more or shut up.
This happens quite often in every facet of our society.
I don't think it's actually that unreasonable to have a gut reaction to yet another language, yet another toolchain, yet more processes to run on your code before it's actually usable. The language has to make its case. And yes, if you don't listen to the case then that's on you.
It filled a very obvious niche.
Point #1 might be the strongest point, but it also assumes that your entire dependency graph is also type-safe, which is rare currently in the JS world (though that seems to be trending upwards).
Point #2 suggests that the class of bugs that were caught on the front-end could only otherwise be caught at compile-time by TS. In this example, its really just a question of whether you want syntax/semantics in your code to prevent these bugs or unit tests to assert correct application behavior. You could easily make the argument that the former is more expedient, I buy that. But it's not some exclusive domain of value in favor of TS. Arguably you could be using both approaches, but I find that increased surface area of unit tests (to cover the type error burden) is de minimis in the big picture.
Point #3 is similarly concerning. I hear this a lot from newer devs, that they can breathe a sigh of relief in thinking they can go make a ton of refactoring changes to their app and know that TS will prevent them from making egregious errors. But in fact, you can easily refactor an entire application and the output can be both type-safe _and_ have the behavior changed such that it no longer meets acceptance criteria.
On balance, if we had native type annotations in JS I'd use them, but I haven't really heard convincing value arguments (yet) to just go all-in with TS.
You're catching a set of errors (interface / types, method signature changes) that are difficult to catch in JavaScript without types when you're refactoring. Intended behavior is still validated with your set of unit tests.
What you've avoided is introducing a new set of bugs that wouldn't be caught without strong typing. You swapped argument order in a private function somewhere, removed or changed a parameter, etc. that isn't caught by your unit tests and introduces subtle errors at runtime that TypeScript catches at transpilation time. That IS valuable on a larger codebase.
You can claim unit tests should / would catch these errors, but rarely do unit tests catch all possibilities. They catch known and probable error conditions. The returns for time writing tests drop drastically as you try to push unit tests into the role of a statically typed compiler.
TypeScript is popular because it's minimal friction while giving you a majority of those benefits.
I want to challenge this a bit. Your unit test suite should also be able to catch all these things with the right assertions. There's nothing about JavaScript, in either syntax or semantics, that make these especially difficult to test for (notwithstanding some of the more famous footguns, but those are well known).
To put it plainly... If I change the argument order of a function/method, the associated unit test absolutely should fail. If it doesn't, I'd argue the test wasn't written properly.
To me, this just smells like an ergonomics preference. As in— do you prefer writing unit tests or type annotations? The latter comes with some transpiling and dependency overhead, so I've just opted for the former.
(edited for grammar)
If I have to hunt down each error caused by each refactor to the codebase, strictly based on a stack trace, I will not be happy. Being able to run a type checker that prints a list of probably wrong spots, and being able to check my unit tests after, makes me happy.
And the overhead of introducing types is quite low, especially when using typesafe database tools like TypeORM and typesafe API tools like GraphQL.
You basically get all your types for free. The question then becomes, why shouldn't we type check?
I'm empathetic to the argument that because TS can find these errors easily, and descriptively, then it's a valuable tool. But your latter comment re: going through a stack trace, doesn't map to my experience with unit tests. If your test output is just a random exception in a stack trace, I would again argue that the test wasn't written correctly. This introduces an assertion burden for sure, ones you don't have to write if youre using TS, but I would posit that the exchange isn't as "free" as you claim.
And "tooling/dep bloat" is not a real thing. You point TSC at your code for prod, https://www.npmjs.com/package/ts-node-dev in dev. Frontend is even easier, create-react-app and Next.js and other project tools have TS support built in.
I also think its interesting that immediately subsequent to your comment about dep bloat not being a real thing, you link to yet another dependency to solve a problem with using TS in node dev.
As an aside for what its worth (since you mentioned frontend), all of my commentary assumes node.js dev and not the frontend. The former is what I work in day to day.
>Why write assertions when TS can figure them out for you?
I could easily just reframe this to say: "why write type annotations when you can write assertions?" — its orthogonal to all my earlier points about the OP's arguments
Perhaps your previous experiences with these sorts of tools has affected your view of this one. I respect that. I just don't think it's relevant here.
> "why write type annotations when you can write assertions?"
Because you don't have to write type annotations most of the time (hence the "figures them out for you" that you omitted). TS infers them, even from things like database or API calls. We're not in Java land, where every type needs to be specified. TS understands what you mean, and will warn you (or configurably, throw an error) if it doesn't. It's a false equivalence.
It does sound like you give the TS community a lot of credit with respect to toolchain stability and thats nice to hear for someone like me who isn't directly involved in that community apart from browsing the issue tracker every month or so.
I'll concede that I do work backwards from the "you don't know what you don't know" bias, and given my previous experience with other type systems, I tend to privilege the status quo (e.g. our software works well, five 9's of uptime, virtually no runtime errors reported via APM, developer productivity is high, business objectives are being met, etc).
I need to work on that bias, because it can become an infinitely recursive argument.
Personally I am very bullish on Deno, which goes beyond just native type system, but we'll see how that project ultimately plays out.
A type system lets you precisely define the structure of your objects in the same language. It's a kind of documentation of implicit information that forces you to keep it up to date and write the annotations in a standardised way. It doesn't replace tests, but it is more precise, compact and you get near instant feedback after every modification of the code. The additional transpiling step might seem like a delay, but only if you don't consider the time saved by the type checker.
When working with other developers it's also useful and gives me the structure of all their objects at a glance.
And for me writing unit tests is pretty much one of the most boring and cumbersome part of development, second only to things like reading documentation in an attempt to figure out why that one DB installation is now malfunctioning since I pushed a commit in a different project during full moon. The fewer tests I have to write the better.
This is much more persuasive than anything written in the article I am commenting on. I personally still don't think this is worth all the tradeoffs for myself or team, but its a good argument for TS.
> When working with other developers it's also useful and gives me the structure of all their objects at a glance
Doc blocks also do this. A good argument could be made such that "if you're cool with writing doc blocks, why not write types while you're there?" - totally agree with that in principle. But again, in the aggregate cost/benefit analysis this is a minor win.
>And for me writing unit tests is pretty much one of the most boring and cumbersome part of development
I retreat back to comment I made about ergonomics preferences. You really dislike writing unit tests, I do not. TDD has been an incredibly impactful (and enjoyable) part of my 15+ year software journey. I started my career using strictly typed languages, and found my way out of them. Unit tests are one of the ways I maintain application integrity, which does actually (perhaps strangely for some) bring me joy. A buttoned up test suite is a very "feels good" moment for me as an engineer. Knowing I, or anyone else for that matter, can come back to the code later and both safely and confidently make changes.
Something also worth noting, that didn't occur to me previously, is that my implicit heuristic analysis here is mostly centered around production node.js apps, not front-end apps. I haven't worked with the latter on a day to day basis in many years. Theres a significant difference in the cost/benefit calculus between the two.
And a side point since we're talking in heuristics analysis... In my experience, especially my recent experience being a director of engineering & cofounder of a large technology company, runtime type errors are just not prevalent in the class of failure modes we see in our applications in production. I can't remember the last time a ReferenceError or TypeError appeared in any of our APM logs. So then the question becomes, how much time is "lost" in development when you hit type bugs (either with new code or fixing old code). This is much harder to quantify admittedly, but also seems minimal in my own experience.
In general, tests can only find bugs, not prove the absence of them. Type systems, on the other hand, can prove that certain classes of errors cannot exist in the code. They are complementary, you want both.
That being said, I don't think TypeScript's type system is sound, so it's probably not really giving you that benefit.
Curious if you could elaborate on why you don't think the TS type system is sound? I'm not familiar with any arguments to this end currently.
Just in general, the TS team optimizes for a balance of bug catching and usability. It is a compromise.
Flow is a much more advanced type checker that works even harder at inferring types and eliminating bugs at the type level. It's very cool! But its community is much smaller, the tooling isn't nearly as good, and it has some major performance issues. My workplace is migrating away from it in favor of TS, which is good enough without being too slow or incompatible with our tools.
You need to test behavior even when typing is correct, even if behavior tests will often also reveal type bugs.
Still, type checking is more reliable at catching type errors, and does so earlier than even run-on-every-save unit tests. But it doesn't remove the need to test to assure correct behavior.
It seems to be about 50/50 and I get a little ego boost when it’s not my fault. Go is no fun, when something breaks in Go I know for sure I did something stupid.
My favorite is when Typescript/Angular magically fixes itself, which I swear happens even though nobody believes me. Sometimes I think JavaScript held together with duct tape might not be completely fool-proof.
That said the “tele-sense” or whatever it’s called that tells you the errors as you type is truly awesome and so I put up with the weirdness and occasional wasted time.
tsc by itself is amazingly fast. Writing stuff in TypeScript has huge benefits, you can still publish your packages so they work in plain-old ES. Interfaces that define structure of objects go a long way to reign in a codebase of unknowns.
Also, various benefits when editing: Type-based checks via tsc and linter, autocompletion (life saver). These things add up quickly in a big codebase.
That's not to speak of utility functions: https://www.typescriptlang.org/docs/handbook/utility-types.h...
I will nitpick one area: generics. While I use them a lot in practice, I seldom write new ones. Example:
In DefinitelyTyped's definitions for @types/react, it makes sense to have a generic in React components. This lets the user pass-in their own interface for their props, e.g.
const MyComponent: React.FC<{ isFirst: boolean }> = ({isFirst, children}) => (
<div className={`${isFirst ? 'first' : 'after' }`}>{children}</div>
)
Declarations for React's FC (points to `FunctionComponent`): https://github.com/DefinitelyTyped/DefinitelyTyped/blob/9d56...In the case of a widely used library, generics make sense at scale. It's reused and components revolve around the shape of props. Not to mention, DefinitelyTyped's superb community, continuous integration and maintainers around the globe.
Microsoft keeps pushing open source forward in so many ways.
Anyway - Outside of that, I've come to eschew writing new generics internally. The reason why is complexity not being worth the trade offs. I'd rather use generics already available (see utility types above). Less cognitive overhead.
On the front-end, types are just too cumbersome, especially if you’re doing Agile and demos are part of the workflow. So many changes, it’s a total drag to rewrite.
On the backend, changes aren’t usually so drastic, so the type tax isn’t so bad.
And yeah, there is a cost to using types. This war was fought long ago. In the end, as always the answer is: “it depends”
This greases the wheels for you with excellent IDE support and a plethora of bootcamped developers/interns.
But there are two things that still bother me: First, it's the usual language-as-a-product release cycle we have these days. I'm not saying that you should have a new language version including ECMA/ANSI spec every decade or so, but new features coming in every few months/weeks causes some friction. Especially in a language that tries to play catchup with free-form argument passing by trying to encase all that in semi-proper types.
And the second and major one: There's a certain kind of "smell" that use of TypeScript causes for me. On the frontend, it often means that it's way too complex, needlessly isolated and probably should be replaced by other techniques. And on the backend -- why are you using TS there in the first place? Compatibility with JS shouldn't be an issue there, you're not running this in your clients browser. Just because the frontend bloat I just mentioned caused Google to tackle this by throwing all the SmallTalk VM writers on this peculiar sacrificial pyre and you got a speedy runtime out of it?
Your comments about frontend TS don't seem super coherent to me, but if my read is correct you just don't like that there's a bunch of code on the frontend. What does TS have to do with that? Even a short program will benefit from type checking.
Your comments about TS on the backend make even less sense to me. It's a language with a fast runtime, but it's also easy to develop with and has some really delightful tooling. If you don't like it, you're not required to use it. If you don't like JS, what does that have to do with the type checking of TS?
Either I'm misreading your comment or the comment is incoherent; either way, I don't get it.
I've just seen way more TS code that looks like Java than code that's C-styled Haskell (and we have/had transpiled languages that were more aimed at this, like Elm or Purescript). And also way more programmers that can do the former than the latter. This really isn't my major area of complaint here, if at all. You have large projects with plenty of programmers, so you both want to express the common micro- and macro-patterns and have solid IDE support to help you analyze and refactor that.
This isn't a bad thing. I sometimes wish it would be different, but that's my "spare time philosophical meandering" part, not the part of me that gets me paid.
The whole shebang of TS add-ons, its ES20xx underpinnings and the tooling surrounding it is well suited to bigger programs. I enjoy programming it it more than some other languages. The ambivalency of my initial paragraph isn't a reflection of TS's specific weaknesses or strengths, but with some organizational paradigms.
And yes, type-checking can get you something even for small programs, and all the people doing it to make their laser-focused progressive UI elements bullet-proof can be happy and proud about it, but I doubt that TS would've been written just for those lucky few.
As it is -- and that's why we got blog posts like this -- TS is often a step for bigger frontend projects, either from the start, or migrating ones that grew a bit too much. Again, a good way to use the language, and better than plenty of alternatives. Is it better than not having a lot of those problems in the first place because might not always need a SPA or heavy-weight components?
>It's a language with a fast runtime, but it's also easy to develop with and has some really delightful tooling.
Compared to Java/C#, if you're doing orthodox enterprise, or Elixir/Rust/etc if you're not? I mean, with esbuild etc., it's not as bad as it was a few years ago, but without the argument of "full-stack", I wouldn't see myself picking it in a clean room situation.
TS to me is a nice to have feature on top of JS.
My point is, if the type system doesn't work with Javascript, then it's the trouble.
It’s not clear it’d be worthwhile, anyway - TS doesn’t support algebraic data types directly, which is the big use-case for pattern matching; and the type system is smart enough, with some careful massaging, to do exhaustiveness-checking when using ifs or switches and the standard pattern for simulating them.
There is currently a Stage 1 proposal for a pattern matching syntax presented to TC-39. If it reaches Stage 3 then it is presumed likely Typescript will implement it.
And if you write code in typescript the number of people who can easily understand your code is dramatically reduced. The pool of people you can hire is now dramatically reduced.
In my opinion the cost/benefit analysis to too high at this point.
Coming from a C/C++ background originally, quite frankly I don't see the supposed benefits of static typing, and in fact I'm intimately familiar with how type systems can be abused to terrible effect (on code quality and developer morale). IDE support is great until your IDE is a bloated mess (Jetbrains, Visual Studio, etc.).
I have recently picked up ClojureScript and it's a lot easier to understand than TS and incredibly productive to use in real life. No static typing in sight. Just my own little field report, I don't expect to convince any hard-core TS fans out there to switch. Do what feels right!
Some of the special types are tricky and make my head hurt but most people will just consume them through autocomplete.
The only thing I miss from transitioning our frontend from React to Blazor is the typescript type system which I always thought was a joy.
I have definitely seen type definitions tortured within an inch of their life, especially in third-party libraries, with multiple layers of generics and utility types and unions until it does take a 9000 IQ to parse them (or worse, figure out why a given data structure is throwing a type error).
While I've come to enjoy the "safety net" and autocompletion of strong types, I've discovered what I really want is meta-programming guard-rails. For instance, while I can define `type Foo = 1 | 2 | 3`, I'd like to be able to do something like `type Foo: number = x => x > 0 && x < 4`, with arbitrary meta-logic that be checked at build time, no differently than preventing coercing a string to a number. The full scope of what I want is probably NP-hard (`1 | 2 | 3` does not actually prevent `foo += 3`!), in addition to the constraints of the JS compile target, but I can dream. :)
If you're smart enough for Clojure then you're smart enough for TypeScript. That doesn't mean you have to like it though, but you don't have to hide that distaste behind incendiary exaggerations about TypeScript only being suitable for superhuman geniuses.
The overwhelming evidence is that TS is popular _because_ it is a safer and easy-to-understand alternative to JS.
I'm a solo dev-- TS probably gets a lot more useful when there's a team of people working together. That would justify the barrier of entry, which was too high for me.
Firstly, the simplest problem to fix with typescript would be to remove the "any" type, with that in the language, it lets lazy developers be lazy and negates the fact that you have typing in the first place.
Secondly, and this is the difficult one to solve, the types can lie to you. Typescript would need to implement a runtime checking system, and i dont see how that can be done without significantly hampering performance and bundle size. I know you can do type guarding and the likes, but you can do that in regular JavaScript as well.
Regarding the "any" type, it's a crutch to migrate existing codebases that started out without Typescript. Every team I've been on I advocate against using it in production code; some teams are better about this than others but it's up to the team how lax or strict their implementation is
As for runtime type checking, Typescript has not set out to replace the JS runtime. JS remains the lingua franca for dynamic content on the web, Typescript can help you write it better and incur virtually no overhead from it by compiling to plain JS. When I worked with financial applications we followed the strictest typing we knew to implement and in my time there (2.5 years) we had zero runtime bugs. It's a great tool and it works
I remember when TypeScript was first introduced. 'any' is what allowed it to become the player it is today, because you could use the entire JS ecosystem with very little rework. If you'd of had to redevelop everything up the stack to primitive types it never would have gotten traction.
How is this any different from strongly typed languages that ultimately resolve to 'object'?
Your arguments sound a lot like letting better be the enemy of perfect. TS doesn't have to have runtime type checking (I'd argue it should not) to be a huge productivity gain.
I guess i might just have to go on the same journey as OP...