How an Anti-TypeScript “JavaScript developer” like me became a TypeScript fan
chiragswadia.medium.com
chiragswadia.medium.com
I honestly find this attitude horrifying. I'm glad the author was able to move on from this, but it is utterly pervasive in some parts of our industry.
As far as "engineering" goes, specifying your types is about as low-hanging, basic a step as you can take. If this is "over-engineering" then I think that says a lot about how much thought, design and engineering goes into some of these code bases.
Even at large companies, there's often pressure to deliver quickly and often. Throwing out large parts of the UI and starting over to please the marketing department is SOP at a lot of places too.
If it were a normal desktop application, they would throw a 30-man team at the problem, but because "it's just a web page/site", it'll be a team of 3-5 people and the time tables will be pushed up.
When you combine these problems, it's no wonder that a lot of JS devs view almost anything that isn't a new deliverable as overly-optimized.
> Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. -Knuth
It's like that cliche "it's only a few bad apples" from the proverb "a few bad apples spoils the bunch."
There is a subset of JS codebases that are good, well engineered and written by people that understand the languages faults and limitations but the list is incredibly small and even smaller still now Joyent isn't really around anymore.
If there was one ecosystem I wish I never needed to touch again it would be JS but unfortunately it's become so pervasive that isn't really a tenable position anymore.
This is the money quote of the thread for me. From the perspective of someone whose main language has been JS or TS for about 8 years, it's getting worse since some of the biggest names in the discipline just seem to be people with large social followings giving terrible advice. This advice is then eaten up by hoardes of bootcamp alumni who have been given a loose understanding of React and unleashed on the world as "junior developers".
But maybe I'm just cynical.
It was great while it lasted.
Eventually, he bites a spoiled sandwich with mold and drinks spoiled milk.
That is the best analogy for npm.
In Java, the engineering culture is strong but stagnant.
Python is toxic, it's full of scientists, the engineering is bad (but a little better than PHP because the community is of a higher calibre).
I'm happy you bring this up. Us developers have a tendency to blame bad code on poor tooling/languages or on individual developers or organizations. Not enough attention is given to cultural factors within various ecosystems that systemically induce bad coding practices.
Java has incredibly high quality tooling because the engineering culture demands it. (also they tend to pay for it which creates a market. I myself pay maybe ~$400/yr total for my professional tools, Jetbrains Toolbox, Yourkit, etc)
The changes to the language and runtime also follow from this.
I would say Java and it's periphery ecosystems show the most promise in 2020 bar maybe Rust. There is also great things going on in C#/.NET space but I'm less involved there than I was previously so can't speak to how fast it's moving.
But I don't enjoy it. I like writing JavaScript. I think I can often do it elegantly and that the perceived elegance at least somewhat corresponds to objective quality. TypeScript makes my code uglier, to my taste at least, and makes development less fun for me. This impacts not just my feelings, but also my productivity. TypeScript had better provide me with something great to make up for this. I rarely make the kind of errors that TypeScript is capable of catching or feel like I need the help with readability and code discovery that it provides. But other people, I recognize, do benefit from it sometimes and in any case, other people pay me to use TypeScript.
I was relieved to find out that they were smart enough to use TS, yet horrified when I looked at the code base and found that the entire codebase was littered with the "any" keyword, making typing damn near useless.
We had so many bugs reports that essentially boiled down to "this data is being transformed in a way it shouldn't be". Almost invariably these bugs were due to the developer thinking that some variable represented one thing, when it really represented something else, and they thus performed an incorrect transformation on it. Typing would've eliminated this entire class of bugs.
Figuring out what any variable represented often involved traversing the huge codebase to find out where the variable originated. On some occasions, I even had to talk to the back-end developers to figure out what the heck was going on. Typing is self-documenting, and would've completely eliminated the need to go on a scavenger hunt to figure out what a variable meant.
Anyways I got sick of this rather quickly, and made sure to add types to any new code that I wrote. I was the only developer on the 20 person team that bothered to do so.
As an intern, I didn't understand why these developers refused to use types. I just assumed that with their years of experience, they must've had a good reason. Now looking back at it, it's clear that they didn't understand types, didn't want to learn how to use types, and thus just chose to completely ignore typing.
As you said, this is utterly horrifying (especially for a financial services company dealing with real money).
I ended up opening a PR to strip out TypeScript from all the Vue components so I could get things up to date (and ease a possible transition to Vue 3, which behaves much better with TypeScript).
I'm starting a new job soon and really hope I can work with React and TypeScript again.
This year, when I came back to the code. I converted everything to typescript. I made sure that every variable that my manipulation worked on had a type. It not only helped me remember how the code worked but it made it so much easier to understand it and modify it without fear of breaking things.
It's been super useful and I don't think I'll be writing any big programs in the future without it.
"... concepts like Generics felt very hard to understand ... every piece of code is strongly typed and overwhelming. Even simple code like below scared me ..."
The author isn't claiming to have had an informed opinion of types. They're saying that they found learning about types to be difficult and stressful. That's a real problem that I think people who understand types forget.
> specifying your types is about as low-hanging, basic a step as you can take.
No, absolutely not. Learning an entirely new syntax, static semantics, and entire way of reasoning about code is about as high-hanging as you can get. Sure, once you know how to use static types, applying that knowledge is fairly easy. But it's a big mountain to climb before you get to do that.
This is the key advantage of dynamic types. You can make a computer do useful things without having to invest the large effort required to learn how static types work.
Now, personally, I think it is worth learning that. And, certainly, once you learn it, you get to amortize that benefit through the rest of your career. But there's a lot of people standing at the base of the mountain, realizing only how far they have to climb with no idea of the view they'll have once they're up there. Those people deserve sympathy, understanding, and encouragement to climb. Not criticism and mockery like I see all over this thread.
No, they're saying that types (which they don't understand) are "over-engineering". This is, frankly, not acceptable to me.
> I'm glad the author was able to move on from this, but it is utterly pervasive in some parts of our industry.
(Edit: added clarification of which speaker's tense was important)
I don't understand how one can be a great (or even half-decent) SWE with such an aversion to learning something so common and foundational as types.
I have no aversion to learning something when it shows potential value, but you must explain the value without gimmicks. Maybe typing hasn't been well sold to this person. The new thing I learn should make my life easier, not harder. If you don't apply a filter, cargo cult and marketing nonsense will sweep you away in a world hidden in unnecessary complexity to make a few bucks.
If learning and using something new adds more stress and difficulty and little-to-no benefit than life without it, I'll pass. When it comes to typing... I don't think that's the case. Typing can be annoying when you first use it (I'm sure I complained a lot) but it's well worth it when you need a stable maintainable codebase.
I think it's a shame that learning about (even just basic) types is being taught as some new thing that can help to make your code better, rather one of the fundamentals that we should be learning from the beginning, which can still provide benefits even when writing in dynamically typed languages.
Perhaps it's just a case of bad marketing and lazy educators.
Could be any of: data structures and algorithms, complexity analysis, operating systems, parsing, compilers, databases, networking, user interface programming, OOP, FP, MVC, markup languages, the web, graphics, manual memory management, pointers, computer architecture, etc.
Respectfully, its really hard for me to wrap my head around this mindset.
When I wrote my first line of code, almost 10 years ago, one of the very first concepts I learned was types. You know the basic OOP lessons where they teach that a Dog is a type of Mammal which is a type of Animal, etc. Typing was ingrained in me before I wrote any meaningful code. So from my perspective (and the perspective of anyone who learned similarly), typing is basic, foundational knowledge, and programming without an understanding of types is akin to running before you can walk
I think a lot of the developers learning nowadays are learning to code only on JS. And on JS you can do a lot without ever thinking about types. So they get stuck in the mentality that typing isn't necessary, and don't ever put in the effort to learn types.
Learning about types isn't that hard. But if you've been conditioned into thinking that typing is esoteric (perhaps due to modern coding courses completely glazing over types), then you're likely going to find it very challenging.
It seems to be a lot harder to get people who learn dynamic typing first to learn static typing, than the other way around.
From a pedagogical standpoint, if the point of an undergrad education was to produce people with well-rounded abilities in their field, I don't know that switching introductory courses to Python was the best move.
Personally, I'm thankful that the first program I was enrolled in allowed me to "fail" fast, so that I quickly figured out that I shouldn't really waste more time and energy into the program and switched into CS. It's a lot easier to pivot as a freshman in your second semester than as a junior finishing the academic year. (And I knew people who took that long to switch majors, who either spent way more time and money or dropped out.)
If we drop down to the lowest level, basic-programming-types are about one thing - interpreting what is otherwise a pile of bits in memory. Without any notion of the difference, how would you interpret the difference between characters and integers and floating points? There is nothing about the ones and zeroes to tell you. So the first thing you learn is to write something like `char a = 'b';`
Dependent types (and related type theory constructs) do get used for this in languages built from the ground up with that as a theoretical foundation, but they have their basis in ideas that are more related to formal verification and logic.
And that's where the divide comes in.
Using types to pick a codec for some bytes and using them to verify systems are very, very different mindsets.
And we simply call both "types".
That way if you later try to call the function passing an int when it expected a Book, you have an early warning system at compile-time. Not run-time.
For everything else, there's type inference.
> When I wrote my first line of code, almost 10 years ago, one of the very first concepts I learned was types.
Well, that's great for you. When I wrote my first line of code, almost 20 years ago, the first thing I learned was string interpolation. Then a long, somewhat embarrassing course in what happens when you interpolate strings that weren't of the same "type". If you're introduced in any kind of self-taught way to programming, and you start with dynamically typed languages, you're doing stringly typed stuff.
Lucky for you. Not everyone did.
It's very hard to remember what it felt like to not know something and likewise hard to remember how hard it was to learn something once you do.
I think they're also shoehorning their experiences. Some coders are cogs in giant tech companies that emphasize code quality, others are on teams in large business in an unrelated field, others are solo hobbyists, and still others are the only tech guys at their medium or small business. Some write code to run a business, others write code to probe the mysteries of the universe. Some write life or death firmware that'll survive for decades, others create landing or promo pages that will be deleted next month and are subject to the whims of the marketing department. Those are all vastly different contexts for a programmer and each requires its own set of trade offs vis a vis typing.
For example, I couldn't care less about strong vs weak or static vs dynamic typing at my current frontend job because the stakeholders don't know what they want, stuff changes rapidly, and there's little I can reuse from one request to the other but I was a full blown static typing crusader at my last job where I worked on a payment system that had to deal with schizophrenic regulations.
Yup, agreed. I first learned to program in the late 80s (when I was around 7 or 8 years old) using BASIC, and my first part-time programming job in college was a mix of C and Java. All of my EE and CS programming coursework was done in C, C++, or (pre-generics!) Java.
It is really hard for me to put my feet in the shoes of someone who switched careers in their 20s or 30s by joining a bootcamp that teaches node, HTML, and CSS, and helps them find web dev jobs.
Their journey is so different from mine that I have very little ability to guess as to what should be easy or hard for them to do as they grow in their career.
Having said that, I still do believe that everyone who wants to write code should learn about static typing eventually, and the sooner they're introduced to the concept, the easier it will be for them to integrate it into their mental model of how software works.
Reading your comment, I wish I was in the former group of learning to code early on, taking CS and SE courses in university, etc... I work feverishly to learn as much as I possibly can about networking, Linux, hardware, SQL, etc.. along with all the JS related topics I need to learn for my job.
I just wanted to add to the conversation by saying that there are people in the later group who really want to become well rounded and not just be JS developers.
1) It is completely understandable that, considering how easy it is to for a new programmer to get started in a dynamically-typed language (and that those languages are pushed very hard as beginner-friendly), many developers have a hard time grasping the idea of static typing and how to apply it to code, because their mental model just never included that, and changing mental models is hard for everyone.
2) It is absolutely horrifying that this is the state of new-developer education (or the lack thereof) in our industry, and I would put this on the (long) list of reasons why software in general is so unreliable.
I give the OP a lot of credit for writing about his journey, and by doing so making himself vulnerable to criticism. And it makes me really happy that he was able to not only eventually see the benefits of typing, but concretely articulate some of the benefits he found helpful[0]. That, IMO, is a sign of a solid developer who is willing to expand his skills and change his mind when given new information. But from a structural standpoint, I wish he had better mentorship when he started out and had never fallen into the "typing is bad" camp in the first place.
[0] "Making impossible states impossible" is something that I sometimes still struggle to impress upon developers with years of experience under their belts. The compiler is there to help you and catch your mistakes before you even check in the code, if you give the type system enough information to do so.
Imagine a case where you can start to be productive with WordPress within few months, but if you were to learn the whole thing, it might take few years, the value provided would be a lot lower.
I went 4-5 years of my career without knowing anything about types, yet I believe I was able to provide software that benefitted the world.
It's not that black and white.
That's one key advantage of dynamic types, but it's not the only one.
Sophisticated dynamic type systems like Julia's are difficult to model statically. Some idioms would require dependent types if they're even possible at all.
Another case is what Alan Kay calls "extreme late binding" in a messaging-oriented language, as seen in Smalltalk and Erlang.
How do you think a “simple type system”, suitable for a Python-like language, would look?
The type systems that have been developed for Python itself, for example MyPy, are far from simple.
What appears to be difficult is to go from an untyped language to a statically typed one. Which is why everybody should start with a statically typed language so that the psychological barrier never develops.
As someone who followed the author's path, this is spot on, and also what I recognized in the article. Everything else you said is spot on too, I just wanted to call this out as part of the article's would-be audience.
But less people, smaller projects, higher experience levels, and better testing are all factors that shift the balance between effort and expected return. Before TypeScript came around I wrote multiple 5-10k loc apps alone in JS and the number of bugs that TypeScript would have caught in those was pretty minimal. Working on a team of 5 on apps twice that big, I see errors that TS could catch in almost every single file.
Personally, the two things I informally look for are:
* Does the language allow instant-to-a-human static analysis so I can see warnings when I'm passing the wrong type to a call?
* Will this language allow me to leave sections (almost always something I get off the wire) with minimal / no type information in order to come back to it later?
Typing is hugely powerful for avoiding problems. I've done a lot of work in Python, which I think has missed huge opportunities to introduce a more robust type-checking system. My current favorite is Elixir / Erlang, which I think uses pattern matching and static analysis to balance strong typing v.s. weak typing wins. It's interesting to compare Elixir to Elm. Elm has a much stronger type system, which means I almost never pass the wrong thing, but it also makes dealing with unstructured data much more frustrating.
How do you come to such a conclusion when there have been many languages whose success was specifically tied to the fact that they were dynamically typed? Doesn't that indicate a pain point?
> If this is "over-engineering" then I think that says a lot about how much thought, design and engineering goes into some of these code bases.
"Over-engineering" means drawing blueprints when the job is to change a bulb. It does not mean that blueprints are useless. It's about the job and the tools picked to do it. I can conceive of many jobs that fit the scope of a dynamic language and where bringing the big typed cannons could be seen as over-engineering.
Language success does not mean product success. A language is just one of the many things in your toolbox. There's a reason why when many products grew larger, they all are migrating to statically/strictly typed languages.
The pain point was always tedium, not difficulty. At least that's how it was sold. We haven't seen a new major dynamic language since type inference went mainstream (and existing dynamic languages are putting a lot of effort into enabling static typing), and I don't think that's coincidence.
How do you come to the pain point conclusion about static types, rather than prior art static type systems, when the vast majority of tooling around the most popular dynamically typed language explicitly dedicates resources and visibility to its most popular static type system?
Let me pull that out into a block quote:
> [types] made those error prone, unmaintainable hacks really difficult to express
This is, in my mind, the most valuable feature of static types, and one that at least some dynamic typing enthusiasts can't easily get their heads around. Types are like rails that guide you to code which is maintainable. Another very valuable feature is that they serve as documentation that would otherwise never be written or quickly fall out of date in a dynamically typed language. Lastly in importance is that the compiler/type-checker precludes certain errors.
If you're calling a function in C, a lot of the time just knowing the type and argument name is enough to call functions without resorting to documentation
In python, you have the argument name and then... what do you want? A number? Are floats or integers alright? Is it a string? And object? An enum? Are there any bounds on the input data that a data type would've instantly told me? Fine, I'll go read the documentation
Adding types gives you so much more information in so few characters
The real world equivalent is giving plugs specific shapes, so that you don't plug your headphones into a 240V wall socket.
In a large enough team, this is essential to prevent incorrect data from being passed around, and breaking further down the line after causing a lot of damage.
I certainly realize it, and I've seen it once in ten years and it'd have been dealt with automatically by a compiler because they were writing tests for autogenerated code. (Why not test the generator, you may ask - and that's a story for another day.)
> If this is "over-engineering" then I think that says a lot about how much thought, design and engineering goes into some of these code bases.
Does that mean no thought and design went into all the huge protects like Instagram, Netflix and Whatsapp (that use dynamically typed languages)? Design and engineering is much more then just adding types. Clean design patterns, good code, tests and docs are pretty important too. In fact they are much more important then just static types.
I have refactored a large, central chunk of JavaScript code back at HERE. I found and fixed several bugs related to typing as I went along.
A lot of code passed server responses or a subset of them as-is, so I had to read API documentation to understand the inputs of some deep parts of the frontend code. This is why I favour explicitly encapsulating server responses in a data class.
In addition to typing, I favour microtypes and enums. Those go further to prevent unexpected application states. Assert statements are a last barrier against unmet expectations that could break things.
If you want to build reliable software, you must be strict about the inputs you accept. Don't let sloppy inputs propagate.
In fact, several geniuses in Computer Science have historically been of the same opinion (pro dynamic typing).
The author didn't use and understand something, and rather than trying to they instead just defaulted to rejection. It's midly disapointing seeing this in people who label themselves as "Senior".
I for one am building a large CRUD application with dozens of models spanning hundreds of fields, and Typescript is helping me (though not a panacea) keeping things correct.
I'm comparing it to a project I did years ago, first Backbone, then Angular, but both before 'typesave JS' was a thing, and that one relied heavily on reading and comparing keys and a deep knowledge of the domain.
But nearly ten years later, I don't have the mental capacity for that anymore and I need the crutches that typed languages offer me. By crutches I mean securities, safeguards and IDE assistance.
It makes sense for anything nontrivial IMO. Types allow you to be able to free up mental space. You can trust the type checker instead of having to rely on your own abilities.
Sorry for the off topic rant!
P.S: Yes I know source maps exist. They suck/aren't reliable.
or idk just say that no, its the kids that are wrong.
Front-end fads are fickle. At least TypeScript has Microsoft backing it so they'll likely continue to support even if the rest of the webdev world moves on to the next shiny object.
Typescript, meanwhile, is used for hugely popular apps, is used by very large companies, has Microsoft's full backing, etc.
The two are not comparable.
Edit: Clearly I'm misremembering its popularity.
JSX is kind of xml done "right". Differences between children and attributes in xml is very hard to grasp, but JSX has a much better defined structure to it and is super well suited to generate html because of that.
We ported off of Coffeescript when ES6 and Babel came out, and you could start writing `class` and using `() => {...}` function expressions with lexical `this` scoping.
Coffeescript lost because it stood still next to the ecosystem and next to the new Ecmascript language standards, which obliviated its advantages. The only advantage Coffeescript had over ES6 was “some of your code is already written in it”.
Once Ecmascript formalizes a type system, we’ll see the same process happen with Typescript. But, I think it will be many years before that sort of feature is standardized. In the mean time, I’m happy to use Typescript today.
Took over a year to convert to ES6.
JavaScript was a minefield of compatibility problems. Many intentional, cough: Microsoft.
Jquery was awesome because it dealt with most of that
Maybe eventually with WebAssembly...
It will still be a while before all the brownfields in the larger ecosystem get cleaned up (if some of them ever do), but that's no longer a language problem, that's a a long tail ecosystem problem.
Is scoping an issue for you? Just use arrow functions!
Is callback nesting an issue for you? Just use `async/await`!
I love it so much.
Sparse arrays, all object keys are strings (or symbols), crazy coercion rules, the existence of `null`, weird float vs int rules.
---
†: I haven’t checked, but I bet there are some libraries out there that provide proper optionals using Proxy.
Map goes part of the way to what I want. But I really want to control which objects are considered equivalent keys, rather than being limited to reference equality.
It's also easy enough to find or write quick simple Map wrappers that use a hash function on an object as keys when provided (either piggy-backing on the existing Object.prototype.valueOf, which always exists on every object and easily falls back to the reference-based current behavior, and expecting classes to have custom overrides for that, or using a Symbol named function of their own to avoid polluting own-property-keys/name clashes with other libraries).
Typing up constants in Babel JS is so much uglier than it needs to be.
But Coffeescript also introduced things like arrow functions that bind `this` to JS. Transpiling to ES5 allowed JS to evolve.
TypeScript brought something good for the JS ecosystem (although Flow did it too, in a different way) and it doesn't look like anything will be able to replace it at what it does anytime soon.
It adds to countless attempts of other transpiled *scripts to extend Javascript.
They, and Coffeescript in particular, are good historical references to what will happen to TS in a few years.
I doubt Coffeescript ever saw this scale of adoption.
As much as I like Anders work, it is impossible to understand modern TypeScript without looking into tsconfig.json before looking into source code.
Many toolings make it so you don't even need to worry about the tsconfig anymore (though in reality it is very helpful to understand it).
Out of all pull requests on GitHub in Q4/2020, 18.8% were JavaScript and 6.7% were TypeScript.
For reference CoffeeScript was at 0.25%. At its peak in 2014 it was at around 1.5%, when plain JavaScript was at over 20%.
https://blog.isquaredsoftware.com/2020/03/codebase-conversio...
That unlocked the ability to add React and TypeScript code embedded directly in the existing AngularJS UI, using the `react2angular` library, as a short-term migration approach. I was also able to migrate some of our existing AngularJS controllers and client logic to TS to get a bit more safety.
Since then, I've set up a new Next.js subproject that sits behind the existing Express app server, and was able to show content from Next by inserting iframes into the AngularJS templates and proxying those URLs from the Express app to the Next app. That's let us build new features from scratch inside of the Next codebase while showing them seamlessly in the AngularJS UI, giving us a long-term migration path.
We've been using Flow as a type checker but are converting our code to TypeScript for the tooling, ecosystem, support, and hopefully improved performance.
TS has been running as a project for quite a while and a whole lot of folks have a stake in it. I think it's a pretty good bet.
Additionally, I was there when Angular 2 was announced, and the community had no idea what to do or when it was coming out and didn't know whether to dig their heels in and stay on 1 or what the migration path to 2 would look like. I think that's a slightly different story given the context.
The thing is that many good ideas of Coffee ended up being integrated in newer versions of JS, so of course the need for it decreased; one could argue that Coffee's legacy lives in ES201x.
But there are no types in JS, and none planned.
But this article reads like "Developer who only used dynamic typing learns about static types." You could sub in any two such languages and get the same article.
I'm not critiquing any of those languages, in a perfect world any of them in the browser would have been preferable to Javascript. The issue is the tower of dependancies we are building, and you have to stop somewhere.
Somewhere there's probably an optimal expected lifespan of a program where the line between preferring a better language and fewer development dependencies is crossed, but I'm also starting to learn that software stays around years longer than I ever anticipated.
It’s like I’m a metal worker and I get along fine with a ball peen hammer. You come along and give me your claw hammer with great excitement. Sure, it can mostly do the same job, but my ball peen hammer is what I’m used to so I’m sticking with it.
Seniors occasionally rejecting this or that, seniors occasionally blindly accepting this or that are both completely normal in majority of seniors.
JS has only been around for 25 years. I’m a “Senior person” and I’ve been seriously working with JS for less than 5 years.
Unit tests have similar advantages to types: they catch errors early and let you refactor your code with confidence.
But unit tests go even further, because they test the _correctness_ of the returned value, not just its type.
And a massive turnoff for me is that the types are compile-time only.
So you see, if your code is tested, and the types are just partial static checks; I feel like it's just not worth it.
I enjoy using types in other languages, for example it feels nice to add type hinting to old PHP codebases. But in PHP, types are much simpler (less work) and more effective.
If they added optional run-time instrumentation like "spec" in Clojure, I would definitely use TS. But for now, I'm out.
But testing with a weak type system isn't going to give you anywhere near the guarantees you'd get from a well-designed type system in addition to testing: https://kevinmahoney.co.uk/articles/tests-vs-types/
(edit: red flag is a bad description, because he ended up being self-reflective and looking deeper, which is just about the furthest thing from a red flag.)
Then Java is a verbose language in general (forced directory structure, one-class-per-file and yadda yadda) but that's besides the point as you're pointing to TypeScripts types being less verbose than Javas.
2) TypeScript has type aliases, meaning you can write `type MyTable = List<Map<string, number | string | Foo>>` and not have to repeat yourself everywhere.
3) TypeScript has anonymous unions and tuples, so instead of writing `Triple<Integer, String, Foo>` you can just write `[number, string, Foo]`, and likewise instead of `OneOf<Integer, String, Foo>` you can write `number | string | Foo`. These are also built in and very ergonomic, so you don't have to write a OneOf4 class whenever you want to have one of 4 things, and you don't have to write helper methods to convert between different sized OneOfs.
But... teammates were running in to issues with 'new' stuff - like, using 'var'. There were others, but this was sort of the archetypal argument. "Well, it doesn't match the rest of the style of the file/app - it'll make it hard to read". Well... hrm... any new feature literally doesn't match the style of what came before it, because you have new keywords/features/syntax to handle processes a new way. It's definitional. But just because there's a new feature doesn't mean it'll be adopted (for better or worse, I supposed, depending on your stance).
(That part has also gotten better, but especially the Java 8 -> anything later upgrade has been extremely painful because of modularization. Past Java 9 it usually isn't that much of an issue. However, JDK 11 was the first LTS release after 8, so most shops basically delayed the transition until JDK 11.)
VerboseTypeName foo = new VerboseTypeName();It wasn't until I started using Haskell that I saw the benefits of knowing exactly what I was working with. It also helped me be more deliberate and thoughtful as you had to define everything up front.
No, red flag is fair since it's just a warning sign. Even if we take it as a mark on the guy, it's true that he ought to get outside the scripty bubble.
If this article started with legitimate reasons for not wanting to adopt TS it would be different. "It is hard for people to learn" is actually a legit reason but "I don't understand it" is probably not!
That doesn't imply all new things are good or that senior engineers need to understand all new things either. I think there is some confusion about this.
Skepticism !== reject everything without grokking.
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.
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
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.
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)
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.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.
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 .)
I don't know of any language that can stop someone from being a sloppy coder.
The code doesn't even throw runtime errors, but you wouldn't be able to tell something is wrong unless a) a request providing the json string actually responded with the error string in the example (an edge case that is very easy to overlook during regular development) and you managed to catch the problem in tests, or b) you specifically went digging into the file that defines getThing looking for the implicit any cast that the compiler didn't tell you about or c) there's a compiler update that turns that any cast into an error
What others are saying is that Java and friends do c) (throw compilation errors when implicitly casting between incompatible types)
[1] You can implicitly do unsafe casts using arrays, but arrays aren't really using generics, they're a special-case builtin.
It'll just return an empty Thing right? No ClassCastException.
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).
* Zod: https://github.com/colinhacks/zod * io-ts: https://github.com/gcanti/io-ts (for the more functional-programming-oriented)
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.
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.
A downside to this is that you need to compile type changes before you can use the change in code, but it gives us a single source of truth for our type definitions and let's us generate a more exacting request and response verification functions that use the (sometimes) more precise open-api definitions (eg a an array with a maximum number of elements and specific string formats), and it's really easy to add a run-time type check to the result of calling into a 3rd party library as well.
For runtypes, you could write a script to use its Static function to spit out the interfaces in pure typescript and then use something like typeconv or ts-to-openapi to make your openapi definition, and then use that script inside your build script to output the openapi schema.
If you've got an example I'd love to see it. I've long been not a fan of OAS-first, but I'm always down to learn something new.
So the way we do it is a little meh because we have two separate run-time type validators, but it's because of the path we took where we adopted request and response validation almost from day 1, and other runtime validation came much later.
We're using express and express-openapi as the base of our project. Express-openapi has built-in request validation and type-coercion, and an example of how to quickly bolt on your own response validation, so for request and response validation we're using that. And it works. It's damn solid. But the docs for how to use the underlying tech to do similar run-time validation for say pulling values out of the db is lacking and I don't have enough expertise in the underlying tech to stand it up quickly, especially compared just dropping in typescript-is changing tsc to ttsc and adding an tsc plugin to our tsconfig.
If I were to do it again I'd either bite the bullet and spend the time figuring out how to get express-openapi's request validation infra to also do general type validation on just the schema, or i wouldn't use their run-time validation at all and just make my own middleware to do it, just so that I've only got a single run-time validation library. The nice thing about express-openapi is that since it "knows" the open-api definitions it can programmatically apply the types needed for validation, so rolling my own would require that functionality, because I don't want our team to have to remember to add the same middleware to every endpoint using a factory function that takes the typescript-is validator function as an argument, and the nice thing about typescript-is is that i can give it a typescript type and get a validator function exactly where i want it with no muss.
Anyway, our build looks like this: 1) compile typescript 2) compile the openAPI to a single file (we put the endpoint definitions in the endpoint file) using express-openapi (just initialize and then write out the JSON.stringify of the apiDoc field) 3) run dtsgen against the output of step 2, and pipe that through `sed 's/declare//g' | sed 's/namespace/export namespace/g'` and into some file.ts 4) compile the file from step 3 with typescript
step 1 gives us typescript compiled to js step 2 gives us a single openAPI.json file we can pass around or import into postman step 3 gives us all the types from openAPI in a ts file with a bunch of recursive exported namespaces step 4 gives us those types in a .d.ts file
We do 3 and 4 in a subdirectory so that we can publish the types as an internal package, which means we can give front-end a types package to consume for all the calls we want to make to backend. Well, we actually publish an sdk package that wraps the types and encapsulates the logic for making the HTTP request to the right endpoint, so we don't have to worry about calling POST when the endpoint is PUT or fat-fingering the response type cast through the entire front-end and can instead just call a function with well-defined types. Plus, now it's super easy to test the interface, because we have a single thing we need to test. So we write an exhaustive happy-path (ie 2xx status codes) integration test against the sdk that actually calls into backend gets the response and checks the response against the type and then the values we expect for each field.
- 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)
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
> 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.
Metaprogramming shouldn't suck so much that this takes hours.
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
Being able to encode/decode IO safely has been a huge blessing when working with JSON.
So Zod it is. I like it.
It will make it a lot harder to return wrong types from your calls. Personally, I like gRPC + Protobuf.
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. . .
You can implement the JSON parser to be type safe. For example, this is how nest-dto can prevent this.
One of the projects I worked on during my transition to typescript was to convert an existing project into it. Sure there was a lot of head banging against walls at some point to get the types to work out, but during that process I finally really understood how everything fit together and gained a sense of assurance that I could use a return of a function with complete confidence. Developing on that project afterwards was also a breeze, moving faster and more confidently knowing that the object types were set, and that guardrails were in place to prevent myself from shooting my own foot.
It's a new system to work within the guidelines of, but guardrails are there for a reason, to prevent you from falling off a cliff.
For me, typed python saves me a bunch of time.
I've barked up that tree so many times, I don't think I'll ever go back to Python for that stuff in the future.
We spent more time screwing around with our Typescript types, getting the compiler to be happy, working around bugs with type assertions, etc. than we did focusing on the app logic at some points.
This kind of stuff, to compare, never really happens with C++. The type system is simple enough (compared to TS) that you can pretty confidently fix type errors quickly. I don't like Rust, but I know that the same occurs with Rust's type errors, too. Why is TS so difficult?
Because Microsoft created a type system that is so complex it can represent (almost) any type in a dynamically typed language. It's impressive, but not at all practical.
I was afraid to introduce complex generic types because it isn't easily represented in typescript, or to change a struct from "person" to "employee" will need to map each properties individually.
Thankfully that wasn't the case.
Once it starts getting any bigger than that, though, static typing really catches and points out a lot of problems before I even launch the program, that I'd otherwise be spending time debugging and looking for anyway, most likely.
I also think static typing has the benefit of enforced self-documentation a bit, so other developers know just by checking the type what sort of data they should expect to see in there, and not trying to give it garbage to begin with. Granted good variable naming and proper documentation would alleviate that, but that's never been fully consistent at any organization I've ever worked for.
And considering I mostly work with relational SQL databases, there's no escaping typing on the database end for that, so I might as well have models that map to the typing of what's in the database so I don't have to keep checking the database tables to make sure I'm giving it good data.
It's a pain in the neck when something starts out as bla_list or whatever and you discover that after half a decade of changes bla_list isn't actually a list anymore, it's a dictionary, and that it doesn't only hold Bla these days, it can also contain Foo or Bar. This type of stuff is immediately obvious with modern static typing.
I don't always change the names if their use drifts a bit either because I know the other developers might be used to the code where it's named something else and it might confuse them, and/or I have to rename it in a bunch of places that might make a merge particularly messy, especially if I'm working in the same place that I think another dev might be working on at the same time (it's happened more often than you'd expect actually, I've had to do some particularly gnarly merges at times).
Blaming the original author for the actions of the maintainer is not fair.
I often write the program in TypeScript types/interfaces first because it forces me to think about the data flow both in and out. In my opinion, data, not code, is the most important thing about a program.
start with interfaces and let the live type checker tell you where your incoherences are. Very very smooth way to work.
In my experience, in general, if you don't already have an idea, you should probably be spending the up-front time thinking about it. If we're talking React/Redux, then carefully thinking through both data representation and what goes into components' properties/states helps a lot for designing a clean and understandable structure.
In the early days of a new app or feature, you have no idea what the correct data/type shapes are.
In TypeScript you can define the types and then easily do major refactors even if you have 1000's of lines already written.
It becomes so tedious to do the same refactoring in JavaScript.
TypeScript makes it trivial to change an object or function signature when developing new code (or old code).
Refactors that take 3 minutes in TypeScript take 3 days in JavaScript and you better hope you catch all the uses.
The refactor ability that TypeScript gives you makes it so much easier to change data types and function signatures dozens of times until you get things right.
I don’t think naming is fully alleviated by static typing; however, on the other hand, static typing allows IDEs to offer better refactoring capabilities, since they can presumably build such functionality on top of the compiler’s structured syntax tree representations.
As an example, lately I fairly frequently find myself using VS Code capabilities to rename TS identifiers as my code evolves. It always handles correctly the scope and everything, I’m sure it’d be more error-prone with plain JS.
Agreed about statically typed code being more self-documenting in general. It’s a bit of a double-edged sword in that it may give an illusion that your project is easier to understand than it actually is, but it usefully shifts the focus of documentation from minute details of code itself to subject domain specifics and how you represent them in your architecture.
I don't like TS because javascript is just a silly language to do anything serious with (besides front end), and its so stringent it takes forever to write quickly.
The critique of typed languages being slow to write was somewhat true in the past. However, more modern typed languages (e.g. Swift or Typescript) do a lot of inference and have some flexibility built in. Because of that I think you get many of the same benefits of loose typing while maintaining type safety. One worry I had with starting Typescript was a fear it was going to make engineers over-engineer things, which is a behavior that Java seems to promote -- that turned out to not be true. You can go off the deep-end on generics, but in practice I haven't seen this.
Also with Typescript, you're not so far from Javascript where it's an entirely new language. So if you love Javascript, you don't lose anything.
Then I saw a function that had like 8 parameters, some were just String, or Int, some where synonyms, and I asked him what it does based on the type signature. He didn't know. I didn't know either. Int -> Int -> Foo -> String -> Foo very quickly starts to mean nothing.
Types don't enhance the meaning of the code. if you want to understand your code better, what you're looking for is better names, not better types. As far as understanding code is concerned, in the best case types have the exact function as names, in the worst case they entice people to write 'int x' rather than giving something a proper name.
I distinctly remember that in college I believed the history of programming languages is purely evolutionary in the sense that newer languages are objectively better than older ones. As in - people wrote assembly until C came along and gave them if clauses and for loops and then came Java where you didn't need to manually manage memory anymore and could use classes and then with Python you didn't even need to specify types anymore!
I.e. the newest language with the most everything is the best!
Nowadays it feels like I'm replaying that in reverse - doing less and less OOP, loving statically typed languages, wanting to learn some Rust etc.
I got this impression too, I was just like the Author in that I hated static type checking as a Junior, but as I gained more experience it flipped from "I hate this" to "this is the first thing I implement in a new project".
To your point of thinking that newer languages being "better" than older ones. I think that also comes down to experience. At first I thought the same way, but the most experience I gained the more I realized that each language is a specialized tool for using in specific cases, some tools are very very specialized (WASM for example) while others are broad (like Javascript).
But, I still have an aversion to front-end development itself, because it just feels too _involved_. You have to set up so much, and it has become a lot more difficult since you need to install webpack, postcss, and many other plugins just to do a hello world app.
It's still bearable if you have to do all of that, and you _understand_ what is going on, but most of the time, especially when it comes to front-end, I do things "because that's how it's done".
This makes me feel very insecure about my code.
These days I find frontend tooling quite delightful, Typescript for instance Just Work(tm) our the box.
The python stack is pretty okay though. You just containerize it, and ship it. Some providers will even allow you to run containers as a function/lambda.
I think tooling has mostly addressed this, or at least provided a mostly braindead path for 90% of needs. For example, npx create-react-app $YOURAPPNAME sets up practically everything you need to get a reasonable react app going, at least in terms of the transpiler/set of plugins. It makes very few choices re: actual react libs though (no preferred router, flux, etc). The preact equivalents are somewhat more opinionated, and go a bit farther in setting up routing/splitting nicely for you.
> This makes me feel very insecure about my code.
I get this. While I stand by what I mentioned above, it also makes me nervous that I understand so little of my setup given how much of it was setup automatically for me, which makes me trust the environment less. FWIW, outside of web dev it seems like _most_ people have no idea how their setup works re: compiling a project. How many devs for say postgres understand the make file or configure scripts? Probably not that many. Maybe that's getting better with newer tools like cargo, but web dev always seemed like the exception wrt understanding the bundling environment, and we're just now catching up to the level of complexity that other types of dev have had for decades.
Edit: apparently that was a bad example. Postgres's make file is very simple: https://github.com/postgres/postgres/blob/master/Makefile
The configure script is less so: https://github.com/postgres/postgres/blob/master/configure
It really doesn't have to be that complicated. Granted, I still use a bundler, but it's really easy to set up. I had my friend walk through it all... he seems to think it's great.
I should probably release this project some day.
``` div("ClassName", { childNodes: [], etc: {} }); ```
Then it grew into having a state management system. And now it's easily the best framework I've ever used... super fast, light weight, extremely malleable, easy to understand.
I keep intending to release it, but I know just how much fun it is putting something out in the world... I don't want this project to turn into my life.
If TypeScript was a first-class language, you could do all sorts of great runtime optimizations (perhaps a v8 fork will do this eventually?). In this case I would absolutely jump all over it, even for small hobby projects.
As it is, going from JS -> TS, one might as well just skip the extra translation step and just start writing everything in Go or Rust, especially for back-end nodejs/deno projects. In these languages the runtime benefits makes the abstractions worth the effort.
I recently changed my mind and started to migrate a large JavaScript codebase to TypeScript, in strict mode. I consider my team to be composed of only good developers but still, I found so many bugs. I still spend time on DefinitelyTyped definitions, but if you do more than prototyping or work on a project also developed with other people for more than a few weeks, TypeScript is for sure worth it in my opinion.
I’m agnostic about Typescript. Bare JavaScript is very efficient and optimized language, well suited for smaller projects.
However, when project scope balloons up, then Typescript becomes more useful. It’s way of enforcing descriptive comments a code, than trying to be a type safe language.
Typescript feels like every one of its features has some catch, and it's very irritating.
In the case of Typescript the team were working under very difficult constraints - the language must maintain strict compatibility with Javascript, and the output must be executable by a web browser that has never even heard of the language.
It's not just that the output is well-formed and sensible JS, it's that it's structured so similarly to the TS. With a recent EcmaScript version as your output, it's practically just the TypeScript with the type-related keywords & blocks stripped out.
As much as it could provide more and better features by breaking that guarantee, I think it's a brilliant decision & guideline, at least until WebAssembly is far more mature and the space starts to open up.
Truly the camelcase's back...
But with Typescript it is as if I'm always fighting the compilator, trying to make it happy with whatever code I wrote. Maybe it's just the JS ecosystem being too lax that makes having types for everything difficult? Like you have a react hook and some components that you can compose, and the typedefs just become unwieldy and impossible to get right. Often they end up not even catching what should be compilation errors.
I’m curious - how do Elm and Kotlin solve these problems in a way that balances the often competing goals of “add types to reduce errors” vs “have a high productivity environment where it’s quick and easy for engineers to write new code”?
Maybe putting Typescript it into the bucket of a "better linter" is the mindset to have.
Kotlin and Elm are more functional, don't know about Elm but Kotlin has non-null pointers as a base feature of it's language, not some later bolted on tie-wrapped solution.
More than that, when you use JS in the back and the front, your types can follow from the database, to the api, to react... even to your integration tests or your public SDK.
Add one more column in your DB and suddenly 1000 errors pops, but those are good errors, you just have to follow them until nothing scream and you have done a perfect migration. It can feels negative at first but it is actually very pleasant and reassuring, like having an angel watching over your code.
I'm not a frontend person and I always disliked dynamic type systems. But I vaguely remember the skepticism when Angular moved to TS. To me it made a lot of sense that most projects at Google, MS and the like would hugely benefit from a language that is kind of a C# on the JS runtime. And in 2021 I see TS pretty much everywhere, but maybe I'm missing something?
There's no unanimous consensus that TS is all gold; some people who are fine with types don't like certain TS implementation details and in fact some devs start saying out loud that sometimes it's very difficult to fight TS with certain constructs.
Something that's gaining popularity and what I'm trying lately is using JS + jsdoc annotations more thoroughly, which gives the niceties of TS (when using vscode) without the downsides and heavy compilation step:
https://www.typescriptlang.org/docs/handbook/jsdoc-supported...
I don't think type docs are useless - on the contrary, they are super helpful while migrating. But I think this trend is indicative of a different issue - what we are really seeing is more of a protest that JS needs native optional type support as soon as possible.
I'm a big fan of this as well. Webstorm also supports it.
It feels like the right balance to me, as it gives me a heads up when things don't match, but doesn't require that I go through the tedium that TS imposes once it touches a project.
>I always felt that adding types to the functions/variables and satisfying the TypeScript compiler is an over-engineering and not providing any meaningful benefits.
I mean ... that is a perspective. EJB 2.0 was over-engineered for many use-cases. SOAP is an over-engineered standard for many use-cases. Static typing is not. Maybe for small scripts, static typing doesn't provide that much value (although even then I don't really buy it - why wouldn't you want to know what you're working with at any given time), but for sizable (web) applications that need to be maintained for years, it's critical.
>also it felt slow to work on, as I always used to get some compilation errors which were hard to understand initially, and I scratched my head trying to figure out the problem.
Another interesting perspective. Every single one of those compilation errors was a runtime error waiting to bite you in the butt. If you think solving compilation errors is slow, debugging and solving runtime errors is way slower. I'm glad the author learned the value of compile-time checks.
>The other reason was Advanced TypeScript concepts like Generics felt very hard to understand initially and I started feeling that I am in the Java world where every piece of code is strongly typed and overwhelming.
This right here is the big problem in a nutshell. There is nothing wrong with ignorance - we all start there. But it's one thing to be 'overwhelmed' by something when starting out, and another to being ANTI that something BECAUSE you don't understand it. The author admits he was overwhelmed by TypeScript and that caused him to be ANTI-Typescript. That is something you see in places. And that the author justified his Anti-Typescript perspective by having an ignorant view of Java to boot. The author talks about 'Java world', but does he have any experience with Java development at all?
It's common for web devs to spend a lot of time dealing with semi-documented lumps of JSON. If you don't have any guarantees about the data in the first place then static typing ends up being a lot of pain without much benefit.
Sure, the situation is far from ideal. But when you're in that situation static typing isn't solving your first problems.
Uh huh. Are you trying to come up with scenarios that you think a typed language wouldn't be useful ... because you don't understand typed languages?
If you have to work with crap JSON blobs with arbitrary and inconsistent typing, static typing doesn't prevent you from treating it as a 'dynamic' untyped map. That's true of pretty much every typed language there is, from TypeScript, to Dart, to Java, to C#. I do a lot of JSON consuming in Java and Dart. My preference is to enforce a schema on the incoming JSON blob and parse it into a typed object automatically, but if I can't, then I just parse it to Map<?,?> OR List<?> and work on that level.
At some point you're going to have to write code to figure out the type of whatever property you're interested in because it may be important to know whether the property is a string, an int, a float, an array, or whatever.
>But when you're in that situation static typing isn't solving your first problems.
Neither is dynamic typing. You're in the exact same boat in that case ... except you get the benefit of typing in the rest of your app.
"I always used to get some compilation errors which were hard to understand initially, and I scratched my head trying to figure out the problem. "
Which is like saying, "I got rid of my carbon monoxide detector because it kept beeping at me"
One can write code that would work correctly, but trying to express the types as precisely as one would like may be painful and doesn't necessarily be productive.
Sometimes when I feel it is cumbersome to express my intent in typing, I may feel satisfied enough to make a runtime check, (and perhaps add a note but depends...). Especially if that functionality is used in scoped area (e.g. module) and not exposed to the rest of the app.
When trying to make a generic and flexible functionality, the typing experience seems get harder and more time-consuming for human to grok, and it may not even be that useful when you look how the code is used.
So I usually try to make typing as good as I can by finding documentation related to what I'm trying to do and banging my head to the wall from a couple of angles, and for some specific parts I may give up and type any.
Perhaps sometimes when typing feels hard it may tell that the abstraction is just plain wrong, so there's that plus side!
TypeScript has its place and it's nice points. It's got downsides too. Just as using plain JavaScript does. Stop bemoaning people who don't use TypeScript. Stop lauding people who choose it as the default "because types." Right tool for the right job, and all that.
This hypothetical browser wouldn't perform type-checking, just be able to parse TS syntax and convert it to plain JS, in the same way tools like Babel do.
This would let you use TypeScript without the need for any transformation pipeline in dev. You could then separately run TypeScript in a console to get async checking. Then only when you build for production do you run everything through a compiler/bundler to get a plain JS bundle that will work in everyone's browsers.
type State = { id: "initial" } | { id: "running", speed: number }
const f = (s: State) => {
switch (s.id) {
case "initial": // s.id available
break;
case "running": // s.id and s.speed available
break;
}
}
The problem with Typescript shows up when people with background in Java or C# starts taking oo design patterns into js-land where they don't really belong (patterns like dependency injection, factories, singletons etc.).If you're an individual, putting together dinky little web sites that you don't need to maintain for years - the value of TypeScript over JavaScript isn't as pronounced.
If you have a web application that counts hundreds of thousands of lines of code, maintained by team of developers over many years - JavaScript does not cut it ... at all.
Well designed projects are broken up into manageable, individually testable modules... So codebase size is an anti-pattern to begin with. As for time, you think waiting 15 additional seconds each time you want to test something doesn't add up and waste time? What about having to type up all these extra type definitions? You could have spent that time writing more test cases.
And JavaScript does not make it easy to do that. It also makes it very easy to do it incorrectly and in a way that is an unmaintainable mess. Every programming language can be used to write clean code if you try really hard, but not every programming language will have the syntax, semantics and conventions that will make it harder to do the wrong thing.
If you do try modularize JavaScript, you're going to need a compilation chain to package it all together - so you're not getting away from that.
>So codebase size is an anti-pattern to begin with.
No it's not. What are you talking about? A large application will have a large code-base. You can't get away from that.
>As for time, you think waiting 15 additional seconds each time you want to test something doesn't add up and waste time?
Sure it adds up, but it's a tradeoff. If you're writing a dinky little web app for your grandma, maybe that trade-off isn't worth it. If your codebase numbers in hundreds of thousands of lines of code then you take the hit with compilation so that you can save time in other places. For example, tracking runtime bugs that a compiler would catch, is very expensive, and gets more expensive as the application grows.
>What about having to type up all these extra type definitions?
Heh. That's not a real issue. Programming isn't about speed typing and measuring 'words per minute'. You're not losing time if you need to write 'int i' vs 'var i'. Again, we're not talking about writing quick scripts or throwaway code. We're talking about code that may need to be maintained for years by many different people.
That's not always the case on the front end (it's still possible to code on the front end without a bundler). And it's certainly not the case on the backend (I.e. Node.js).
>> Sure it adds up, but it's a tradeoff. If you're writing a dinky little web app for your grandma, maybe that trade-off isn't worth it.
This could not be further from my own experience having gone back and forth between JavaScript and TypeScript multiple times both professionally and as part of my open source work for over a decade.
I've built highly complex scalable distributed pub/sub systems, P2P systems, blockchains and a decentralized exchange using plain JavaScript in record time with no bugs found for over a year.
I tend to rail quite hard against the “type everything” world view but I’ve always been crystal clear on the benefits of types at boundaries between components. They can be a succinct way to codify the invariants and contract between components in a software architecture - with a nice quick feedback loop when you appreciate it most (when you’re exploring an unfamiliar API). They can also be a total train wreck for productivity when abused.
Where they fall down for me and always become a productivity penalty is working inside those component boundaries. Tests are a better answer then. I care that the actual behaviour is sound, if i say concat(list1, list2); something like Java’s type system guarantees of only “hey you put two lists in, you get some list back which might or might not be a concatenation of the two you provided” aren’t sufficient, you might as well not type it at all. Tests are necessary.
In typescript you could actually type concat in a meaningful way but i digress.
I think tests are also a better tool for handling people boundaries, that is developers separated by time (i.e. myself in 1 year) or separated by space, (i.e. between groups of developers in different orgs). The fundamental difference is the explanation, a test explains how something should behave, an intention. A type is a terse expression of how a person interpreted the problem domain and is not guaranteed to be an appropriate or useful model, it’s just a rule.
There’s a possible exception to all of this for me - dependant types. I’m still learning and undecided on the value.
All said and done, I’m happy that things like mypy and typescript exist, but I’m not convinced they are a cost effective way to maintain reliable software if over-used or used in rubbish ways. Used appropriately, they’re great. Used all the time, no thanks.
https://blog.isquaredsoftware.com/2019/11/blogged-answers-le...
I haven't worked on any projects that were large enough to warrant type checking or where I felt it would make a huge difference. Also I find it a bit verbose and ugly and I'm just not a fan of pseduo Javascript languages that need to be compiled down into JavaScript.
Part of my problem stems from the lack of typed libraries across the JS ecosystem. Party supported typing leads to more issues, I've found.
Then there's the IDE. I don't use code hinting. In fact, I find it irritating. So, I draw no benefit from TS in this regard.
Then there's the cryptic error messages. They've gotten better over the years but I always seem to spend more time debugging TS than writing code.
And finally, I just don't run into the kinds of errors people talk about solving with TS.
That said, I did enjoy the strong typing that AS4 provided back in the day. But TS is intolerable to me and I've worked with it for years.
The issues you are having sound like teething issues but as someone who's worked with TS for years I've found they aren't hugely difficult to overcome, either with @types packages, or writing declarations, or declaring module (and having an "unsafe" library that people use with care).
> I don't use code hinting. In fact, I find it irritating.
I mean that's a personal preference, but if you're a lead dev and you have people starting work on a codebase they have little experience with, it's a godsend because they aren't totally blind.
Very much this. I pushed to move the scripting language for our game engine project dot big bang from JS to TS primarily because the typing makes the entire codebase much more discoverable and beginner friendly. Using Monaco embedded in the browser programmers get great auto-complete and inline code documentation as well as the compilation step making some simple and common runtime errors much harder or impossible to run into. Qualitatively the mixed experience bunch we have using it now all much prefer the TS experience to the JS one we had before.
Can I esk exactly how you're considered senior if you've not worked on any projects large enough to warrant type checking?
And actually you can write very type safe code in pure JavaScript.
1. (arg) => if (typeof arg !== "string) throw new Error()
Of course, this is executed during runtime (which could have benefits too sometimes) and not at transpile time.
Yes, you can laugh at me. But.. You should be always aware of the types you are using, which data is coming in and if the function is type critical, which is only the case for some functions, it is even more fault safe doing it this way, because your app is spitting an error during runtime, which will definitely be fixed very quickly. :) To validate data, use typed structs.
2. You need to write good tests anyway. Tests should test with malformed and faulty data always.
I and the teams I had, maintained and coded big JS applications. I can remember only one case, where TypeScript could have been helped with typing. But also, we had no test written for it, so..
It seems like as of 2021 though that typescript has sort of won and the community has done great work in covering virtually every package I seem to use. npm helpfully points out which libraries have typescript definitions or a @types/<package>. I'm still not entirely sold on the strengths of strong typing in terms of bug avoidance, but the IDE like features are enormous. For me personally I see typescript more as a forced, never stale or mostly-incorrect, JSDoc.
Disclaimer: I'm the author
I dip between javascript and typescript and hesitate recommending it for large scale project for a large team as much as I would like to, coming from a background of typed languages on the backend. There appears to be a need for experienced senior typescripters on the team to prevent falling into certain traps and onboarding [1].
I do wonder though how he saw typing as over-engineering. It may cause intertia which he hints at in the next sentence but over-engineering is a bit uncharitable.
[1] https://medium.com/javascript-scene/the-typescript-tax-132ff...
declare let myColor: Color | undefined;
Suddenly everything turned into Java with signatures cascading endlessly. I'm not blaming TS, it's more of a.. lack of faith to make the full jump? Or limitation of the underlying Javascript? This problem should be solved with having multiple function signatures, in the example above one with and one without the Color param.I've been toying with Erlang for years, once I grokked how they flow control can be done via different method signatures, the Other Way started looking really inadequate.
If it's about optional parameter weirdness and TypeScript's lack of method overloading, I suspect that's inherited from JavaScript's laxness with method signatures. I don't like it either.
IMO not having to do runtime checks is a big part of the value proposition of method overloading the way many languages do it. Whether or not it’s a limitation inherited from JavaScript, it’s still really klunky compared to other mainstream languages.
It’s one of the few things that I actually dislike about TypeScript, despite overall loving it.
let myColor: Color | undefined;
I don't understand what the problem is. Native JS would require you to define the starting scope of the variable (unless you're relying on global scope!).Then you declare what the type is: Color or undefined. The state of the variable can be one of two things. Perhaps I've been writing TS for too long but I think I'm missing the problem here.
It's just like being in Java land where typing adds a lot of boilerplate to the code, but you still have to do null checks everywhere.
The benefit of a type system is that it provides an organizational framework for code bases that need it (i.e. multi year projects involving a rotating set of developers). That's the only argument for a type system that I agree with (I don't believe it reduces bugs or anything like that, and from what I've seen there are zero studies that conclude such nonsense... this is a 40+ year debate we're talking about).
Structural type systems with type inference don't do that. I can't look at your code on Github and know what the code is doing. There is not enough information there. Contrast that with nominal type systems where everything I need to know about a function is right there. I don't need an IDE or external tooling to guide me. It's a true mystery how people think they are reviewing pull requests written in TypeScript and actually doing their job correctly. If you so much as have a single @ts-ignore or "any" in your code base, then all bets are off on correct behavior.
TypeScript diminishes the point of type systems by giving the developer too many escape hatches. You ever see a developer abuse the non-null assertion operator in a case where it's not at all true that the value is not null, but the developer doesn't care because they just want to make the compiler happy? Yeah. I have. Then you have to explain the purpose of the non-null assertion operator and that goes about as well as you can imagine.
If a type system isn't helping you understand your code or organize your code, then it's all rather pointless. TypeScript is incredibly complex. How many people using TypeScript can tell you something as basic as the difference between "interface" and "type"? And try to describe the difference without using sentences such as "well, originally..." and "back in 2017...".
edit: Oh, and in case anyone thinks I'm being pedantic on that last point, check out this absolute shit show:
https://stackoverflow.com/questions/37233735/interfaces-vs-t...
Keep scrolling down for ultimate depression.
I don't think typescript can ever be too strict, there's lots of other regular javascript files and libraries typescript will need to interact with. True it gives you plenty of escape hatches, but the point is to have less escape hatches than javascript. Your team can discuss what should or shouldn't be allowed, I'm not sure how having strict typescript is really solving the problem of mis-aligned expectations for what is allowed code.
Regarding the non-null behavior I've found that the real problem usually isn't with the variable's type definition but with it being a 'global' or not properly defined with the constructor. One can't define it as non-null because they don't know when it is constructed aka they add a property to a class that they plan on adding later when they should probably either fetch that property first or ensure it is done in the constructor.
To be slightly fair you do get similar stuff in say java where interfaces slowly gained default methods and got closer to abstract classes.
I've been writing a ton of TS since 2016 and in most of those projects we've had liberal use of 'any' or even incorrect types (!). It's still far better than vanilla JS. The argument that "it doesn't work all the time therefore it's worse" makes no sense to me.
Ultimately the issues you're talking about make logical sense academically, but nobody has ever raised them as problems in the real world that I've ever encountered.
Full typing makes my job easy, but a little bit of typing is still better (in most cases) than no typing at all. There is an argument to be made about simplicity for small codebases, but those aren't the ones I'm talking about.
At that point, TS is just a burden.
After using it for a few OSS projects and professionally, I came to the conclusion that, if I need types, I'll use a real statically typed language if I need types and JS for what doesn't matter.
TypeScript works but: - it's a major pain to work with if your dependencies don't have type definitions - if another developer in your codebase left a hole somewhere you won't be able to rely on your types, you'll get runtime errors and you basically ruin the capability to reason around your code - it's slow (hopefully deno's efforts will bring some news in that regard)
TL;DR: TypeScript should be much stricter and don't compromise with JS.
TypeScript had absolutely no excuse to keep JavaScript, beyond the misguided idea that because so many JavaScript developers exist that it should be easy to get them to learn types.
First, they introduced a compiler. At that point, you may as well toss out JavaScript for a better language that actually has nice type syntax. TypeScript syntax is horrible. It makes JavaScript completely unreadable in many places.
Second, JavaScript developers tend to be the type that have never learned another language beyond JavaScript. They come from bootcamps and elsewhere that only teaches JavaScript and that's all these developers know. They do not have the experience necessary to not make a mess of TypeScript's structural typing system. You'll end up with incoherent ad hoc types all over the place. You still have JavaScript's ad hoc data flow all over the place, but now you have TypeScript yelling at you while trying to formalize the mess and failing badly at it.
What I find is that often when people say they like TypeScript they really mean they like VSCode.
On the other hand, issues related to parallel async state mutations which involve race conditions are very common in complex projects and are very challenging to debug. Type incompatibility issues are a walk in the park. You don't need a type checker for that. Just because your boss is incontinent doesn't mean that you also need to wear an adult diaper!
I used to think the same. Coming from the Java world (might explain why I like it) colleagues convinced me that it would be better to use another runtime (JVM/CLR) than trying to use types on NodeJs (it was stupid). I used Typescript in the first versions it was painful, trying to find type definitions, writing my owns, etc. So I did not insist and moved to Javascript for NodeJs.
I rediscovered the language 1 year ago and uses it for some frontend (Vue) and backend (api gateway, lambda, mongoDB/dynamoDB) Nowadays I very much like the language, union and intersection types make it very expressive and precise. Compilation (or translation) is a little slow but bearable, and it is nice to have a common language and tooling between the frontend and backend. Most of the libraries provide and maintain typescript definition files when they are not themselves written in Typescript. Sometimes I think the language goes too far in the features, I had a look at the latest versions and was a little bit puzzled by the complexity. But even for small projects to larger ones, I definitely prefer statically typed languages (with type inference and even implicit ones) especially when time is limited to write tests and have a good test coverage.
Verily there is little new under the webdev sun...
Yeah it is quite interesting how everything goes in cycles.
I had no issues with that back when I was a teenager trying to put together flash games, so apparently the only thing preventing me from writing exclusively in TypeScript was a lack of a decent toolchain.
> const x = 'xxxx'
> if(x === undefined) {
> console.log('never happens')
> }
will compile without any problemsI think this is partly necessitated by one particularly large soundness hole in TypeScript, which is that expressions are typed as though dictionary and array subscripts are always in bounds:
function foo(arr: number[]) {
const m: number = arr[3]; // typechecks
if (m === undefined) {
// this IS reachable
console.log("arr does not contain key `[3]`");
}
}
The alternative would be forcing all array and record accesses to have type `whatever | undefined`, despite the fact that almost all such accesses are clearly in bounds.This is in fact now a part of TypeScript behind a flag since 4.1: https://devblogs.microsoft.com/typescript/announcing-typescr...
Adding a compile-time bounds-checker to TypeScript to find cases where the `| undefined` is unnecessary would definitely be helpful and interesting, but would do nothing to help TypeScript's reputation of being difficult to wield and slow to compile.
It's also not an easy problem to solve, due to the semantics of JavaScript arrays:
let a = [];
a[5] = "five";
console.log(a.length); // 6
console.log(a[4]); // undefined (!)So, is there a good book that some of you would recommend, that would have the same impact as Flanagan's, but about TypeScript?
Upsides? When tuned to its most pedantic settings I've uncovered a handful of bugs that would have otherwise launched.
That's worth far more than a few tiny annoyances. tslint > jslint.
I built an electron webpack framework and it uses hot reloads, but since i fork a lot of processes, it doesn't quite understand those need to be reloaded too! Got a fix for that by any chance?
Most people use eslint. But eslint on TS is excruciatingly slow.
I like the language at a conceptual level and find what it does very useful. The syntax I find less than desirable (especially nested generics).
For smaller projects I tend to use just JS because it's faster if I just want to prototype an idea and I'm not planning on keeping the code around for long.
Where Typescript shines is when working with a team on longer term projects.
The generic syntax gets derives from C++ which I don't think is a very good syntax. When dealing with more complex generic type declarations with nested generics / types your eye is scanning back and forth trying to match angle brackets and it is really hard to parse visually. Other languages like Haskell allow you to just read the "generics" left to right. Aliasing the type helps to some degree.
The other thing I don't like is when you are experimenting and fleshing out an idea you want to take a try it and see approach but with Typescript you have to really commit even though you just want to experiment. When you want to change something there is so much more code that you have to change.
The pros are when dealing with code you wrote in the past and forgot about what the types / parameters are or when dealing with code written by other people. Having the IDE suggest what you need to pass it is amazing.
The contrast to that is having to jump to a function to see what POJO style object it will return, what the exact spelling of the property / key are, and things like that. Having static typing saves so much time when dealing with other code.
If your project involves you working with others' code or code you wrote in the past and don't remember well then this is Typescript's sweet spot and it is a huge win.
One of the things I find most lacking in Typescript is pattern matching. If you have strong static typing like Typescript does then pattern matching pairs really well with that. It makes your code much more declarative and easier to read / write.
I've seen this opinion before, and I don't understand it in the slightest.
Let's say you're modeling a "user". You might initially think that users should have a first & last name, so you write it this way. Then later on you realize that there's no point in this distinction, and some of your users from Indonesia don't even have separate first & last names, so you want to switch to a single name.
* With weak/no typing, you have search around for anything that matches the field names, and hope to God you didn't miss anything.
* With stronger typing you just change it let the compiler tell you all the places you need to fix up.
If anything, Typescript is the best of both typed and dynamic languages because you have the option to use types only in places where you really want to enforce them, it's not all or nothing.
When you write TS, you are (literally) writing JS, except that you are also writing miniature contracts (types) which are shared between components of the system and between authors of the code. This helps to ensure that the JS everyone is writing is mutually compatible, and that pieces of JS that should fit together actually do.
The idea that something's not part of the web stack if you can't throw it at a browser seems a bit weak; especially as, in this case, you can throw a subset of what you write at a browser and have it perform as intended.
https://github.com/whyboris/TypeScript-Call-Graph
The TypeScript language service is really neat -- you can use it to parse through .ts files so you can, for example, see which functions call which functions.
I feel that things like using WebAssembly to bring in other languages, or compiling down Ocaml/Ocaml-like languages such as with BuckleScript or Reason etc. is more appealing than TypeScript to me.
Is there some authoritative guide on this?
By removing type annotations you are going against automation. And, as a software developer, automation is your job.
I wonder if I'm missing a better way to do it.
Multiple Dispatch, I love it.
I read way too much code to make myself read code that's not beautiful.
python's tabs?
Indentation's upsides are less line noise. In my opion, while there's less noise, there's less symmetry, so it's a wash on the beauty count.
Indentation breaks many tools, especially the way amateur coders use them. They're frequently showing code snippets by copy-pasting into web pages that strip indentation.
Notably, the standard pylint rules insist on two lines between functions and classes because the extra whitespace helps people find things. That same whitespace you get if you close a function with a single brace...
Keywords are surprisingly nice. Lua does them very well:
function foo()
doSomeStuff
end
You get rid of the pointless { at function, and you have a simple "end" when you're done.That's also why I think it's very funny that JavaScript developers are starting to use TypeScript on be backend, i.e. Deno.
You are using the patch to JavaScript that backend devs needed to make JavaScript more like backend languages in the very place that the languages it is trying to be like already exist. It's ludicrous.
I write large Node projects and Electron apps and TS has been a huge help.
[0] https://web.archive.org/web/20121004030106/http://www.typesc...
TS hasn't changed the way I write applications too much, besides giving me a bit of safety/enforcement of good practices and productivity boost
With Deno, you're using a classic OOP inspired superset of JavaScript, transpiling it to JavaScript, and running it in a JavaScript engine on a server. That's only necessary in a browser.
On a server, if you don't want to use JavaScript, you can just directly use a classic OOP language, and dispense with the whole extra frontend transpilation abstraction.
Unless you're a frontend-only webdev, and, understandably enough, you don't want to learn a classic OOP language, which I believe is the real reason.
Or, at least I hope so, because the only other possible reason is mass obliviousness, and that's horrific.
Classic stockholm syndrome. It sucks but I have to use it to survive so now I love it.
>> Making impossible states impossible
Well if they're already impossible, there is no need to make them impossible.
>> exhaustive checks
There is no such thing in coding. You can never exhaustively check all possible issues.
>> Spotting bugs early
How about learning to write good tests?
>> Rich IDE support and ease of refactoring
Yes, TS makes it much easier to write and refactor spaghetti code with low cohesion and tight coupling. If you have clean code with good separation of concerns, it makes no difference to the refactoring experience at all if you're using TS or JS. Why TS developers always talk about refactorings? If you need to 'refactor' your code, it means you designed it wrong. Perhaps TypeScript encourages over-engineering which necessitates constant refactoring?
A lot of of people are "discovering" TypeScript because it is the first widely used and well supported attempt to bring static typing to a language that is currently the only mainstream option for programming to web.
TS helps if the project is really poorly written... But that's not something to aspire to.