I also feel unit-level tests are less necessary with TypeScript. I used to write a lot of JavaScript tests that essentially just confirmed that "this sh*t is all tied together correctly". With TypeScript, if a code change or refactor broke something structural, you know immediately!
Especially with strict compiler settings, including strict null checks, and the good type conventions they encourage. To give a small example, if you have a value that can either contain an error or a successful result, you can model it as the union of a success and error state. This prevents you from accessing the value until you check the result is successful (and the compiler will nicely infer the type in subsequent usages once you do.)
For me, with rigorous compiler settings and a good proficiency in type modeling, I've reached the point where I rarely have any runtime errors at all once the code compiles. Sure it might take 2% more time to write code and think through my data model, but I spend 95% less time debugging it and the end result is much more maintainable. Even using typed languages like C# and Go now leaves me slightly dissatisfied!
Relying strictly on typing is dangerous as you might've given wrong type from data coming back from database queries and you can't tell until it has run.
I imagine adding types gradually as a design solidifies, rather than slow velocity early on. Best not to be dogmatic on these things.
Types don't slow me down, and I can't imagine working without them.
A wild Idris programmer appears
I'm not sure TypeScript is good enough for doing that (I don't know TypeScript that well), but it's simply much better than starting with code.
Also coming from JavaScript development first, TypeScript made learning C# and templates a breeze.
Fine.
> look how it works,
Beutiful except it doesn't work yet because it's just types that make some shape. You could literally draw the thing with pen and paper.
> go bother with implementation.
And only then discover that something you have thought of as implementation detail is one of the main problems with your solution.
No problem. I'll just refactor some type. It's easy. Less beautiful but at least it'll work. Except it doesn't. Butchering your elegant design for hours you come to hard earned realisation that the shape you imagined that you wrote and carefully named all your types to represent actually has nothing to do with the one you need.
If you are a bad person you ship your mangled typed monstrosity that you just barely made to work with traces of your struggle fossilised for any future maintainers.
If you are a good person you toss everything to into trash (save for few hard earned implementations) and start fresh with knowledge and humility wishing you worked with TypeScript where you can start with implementation and only after you figured out the hard parts, add types that reflect your solution (finding few additional bugs in the process and getting more confident about the thing you wrote).
Paper does not tell you when something you draw is wrong.
> And only then discover that something you have thought of as implementation detail is one of the main problems with your solution.
What is no different from learning that something you implemented is wrong, except in that you spent less time to get the problem. Or do you expect some magical designing tool that will lead you to the right solution before you understand your problem?
With some things it does. At least better than your brain does. Typesystem does it even a bit better. But you end up with a picture not a program nonetheless, before you start writing implementation.
> What is no different from learning that something you implemented is wrong [...]
What is different is that with all the types you wrote, the process of problem discovery that happens during implementantion is stifled by what you wrote so far. Also you have additional temptation to contort your types and to leave it in a bad state because you wrote so much already and you'd very much want to avoid tossing that out.
My point is that you discover problem through experimentation and types make experimentation harder because they make the code in some ways harder to change. TypeScript avoids that by allowing you to use as much types as you want at any stage of problem/solution discovery but not forcing you to.
The only benefit of a plan is that you may discover it's wrong, and it's easier to throw away than the done thing. If you are not willing to reform or throw away your plans, you are doing it wrong.
And you will almost always know what type a property needs to be. Name? string. IsActive? boolean. Last modified? Date. Etc.
Also typescript just has number, so you're not even faffing around thinking "int, float, long, double, decimal, unsigned, signed" when adding a number. Although this is ultimately a bad thing.
Because you have to hit more keystrokes to write your program?
That doesn't compute. typing is the thing you do the least amount of when programming. In all best practices we are taught to not save keystrokes. Name your variables expressively. write small functions. document code. Write tests. All ""excess"" keystrokes nobody questions. But when you have to type " ... : number" it's slow velocity?
In my opinion people vastly over-estimate productivity boosts of saving keystrokes. It's the tedious, boring part of the work that you would like to skip entirely, hence excess keystrokes feel way more "slowing you down" than they acutally are.
Although it doesn't matter, you actualy save keystrokes: autocomplete, auto-refactors and less low level unit-tests compensate the couple of type annotations easily.
That usefulness is diminished when you are fighting the type system instead of getting things done.
Like when you have to make exceptions in typescript by using Any. Like when an interface for a js library is missing and you don't want to do all that work.
Or when an API returns a field with a different type in between two calls and you have to make exceptions in the code when parsing with jackson in java.
Code is read much more often of course. You may argue you can read a fifteen-page book as fast as a ten-pager, but it's pretty clear that's not the case.
Of course tools and inference change the balance, but even so I avoid putting code into the the kiln until the design is fully understood and at least satisfactory. And that can take a few revisions.
And figuring that out becomes a massive time suck when that's a constant across an entire code base. Statically typed stuff means I can very quickly find that out even if it's the first time i'm exploring that particular area of the code.
Some dynamically typed languages support type annotations that are only enforced dynamically; Julia is the primary example of such a language ATM.
Suits me.
Welcome to 1985 folks :-)
I don’t know about the rest of the world, but I’ve never once been slowed down by a compiler saying “It won’t work like that. Don’t waste your time”.
> I don’t know about the rest of the world, but I’ve never once been slowed down by a compiler saying “It won’t work like that. Don’t waste your time”.
I'm right there with you, I really don't understand this train of thought that catching errors faster somehow makes you slower. I like having the compiler there to catch my errors when I make them rather than some number of minutes/hours/days/months later when everything is on fire and I've forgotten the context of what I was doing.
Maybe learning a statically typed language (nearly) first damages your brain irreparably, and there's no longer any hope for me.
Under dynamic typing, you just do a small part of the job, yet the program builds; you can get in there and try things. Decisions about how to proceed can be guided by errors you run into when you step on the things broken by the partial refactoring. Seeing things working right away can be a motivator to get the whole thing done. Or it can help you see that, oops, the change is not worth doing or bad for whatever reason: you dodged a bullet with a small amount of sunk cost (the few changes you made that you can throw away without a whole lot of regret).
Static typing helps with small refactoring where the ripple effect is small. The compiler pinpoins things that have to change; they happen to be few in number, and don't have too many ripple effects of their own. If your change breaks everything, then it's moot; the compiler tells you there are breaking changes everywhere.
There is another angle: under dynamic typing, changes can be done in ways that things continue to work. You can write modules that have "absolute genericity": they work with objects of any type without being recompiled. Such things are bullet-proof against all conceivable refactoring.
I’m reading this and I have just no idea what to say.
With static typing you don’t have to be very considerate in this work, because you will have a compiler telling you if anything is broken, not to mention refactoring-tools which can help you reliably and automatically make those changes across your entire code base in a verified 100% correct way.
With dynamic typing however, you just have to guess and pray.
Clearly static typing is the superior option in this specific scenario where you are calling out static typing to be a disadvantage.
So what am I missing?
[0]: https://en.wikipedia.org/wiki/False_memory#Commonly_held_fal...
A good language has a run-time type in every object, regardless of the type checking system, and allows programs to be run safely even if they are incompletely checked or if inconsistencies have been positively identified.
I have exactly the opposite experience here. If you're making a sweeping change to a core data structure, I would feel much more comfortable making a large change in a statically typed system. It would take much longer for me to feel comfortable releasing a change like that in a dynamically typed system.
> Under dynamic typing, you just do a small part of the job, yet the program builds; you can get in there and try things. Decisions about how to proceed can be guided by errors you run into when you step on the things broken by the partial refactoring. Seeing things working right away can be a motivator to get the whole thing done. Or it can help you see that, oops, the change is not worth doing or bad for whatever reason: you dodged a bullet with a small amount of sunk cost (the few changes you made that you can throw away without a whole lot of regret).
Is this how you work? That's terrifying. How much time do you have to spend poking and prodding after you make a change like that in order to be sure you've fixed everything? How much time do you spend tracking down the source of bugs caused by this weeks or months later?
> Static typing helps with small refactoring where the ripple effect is small. The compiler pinpoins things that have to change; they happen to be few in number, and don't have too many ripple effects of their own. If your change breaks everything, then it's moot; the compiler tells you there are breaking changes everywhere.
If your change breaks everything, it's too much to change at one time. Period. Smaller refactors that deliver some amount of value are so much faster than cleaning up for months after throwing caution to the wind.
To use the example of a change to a core data structure: If you want to make a breaking change to a given struct, the first place I'd look is to see if you can convert between the old style and the new style seamlessly.
If that's possible, then you copy the data structure, make your changes, and add a method on each to convert to the other. Then you can slowly roll out that new data structure to the rest of the application until the compiler allows you to delete the original. Speaking from experience this route is so much faster than swapping it out at the source, fixing it up everywhere, and hoping things continue to work as they should.
If it's not possible, not all is lost, but I'd need to see the specific example to give advice on what to do.
> There is another angle: under dynamic typing, changes can be done in ways that things continue to work. You can write modules that have "absolute genericity": they work with objects of any type without being recompiled.
If your code can work on literally any group of assorted types, but still do something of value, then I'm interested to see what it's doing. Sounds like it doesn't do much of anything except pass pointers around. Great for a collection struct, but that's about it. As soon as you need to get things out of it and do something of value, you'll need to have some idea what type you're getting back.
> Such things are bullet-proof against all conceivable refactoring.
I am extremely cynical of any promise regarding code that is "bullet-proof against all conceivable refactoring". OOP famously promised that for many years, but my experience has been that it's the cause of far more refactoring pain than it saves.
Hold on. Are you conflating OO languages with static typing? If your experience with static typing is Java then I actually think I understand where you're coming from now.
I wish I could be convinced by the likes of Rich Hickey that types don't actually help, because he's so smart and charming and eloquent, but my experience screams the opposite.
The usual saying is that faster development is more important because your business probably won't get users anyways. As if Ruby is going to give you a two year head start before any chickens come home to roost.
I don't know about that. Maybe you will get a half-day head start on me if I'm using a statically-typed language I haven't used before and I'm digging through old projects to remember how to write my .gradle hello world.
I don't want to commit to refactoring a large amount of code for the sake of a small change, just so that some code-writing robot gives me permission to run the whole thing.