But if you write tests already and use immutable data structures, what benefits does TS bring to people whose background isn't in strongly typed languages?
But if you write tests already and use immutable data structures, what benefits does TS bring to people whose background isn't in strongly typed languages?
- It's easier for a machine to read, which means the possibility for better tooling. e.g. linters and checkers, automatic refactoring.
- It's actually faster to write, due to the ease of making changes. If you move a function out to a new module, the compiler will immediately tell you all of the invocation sites that are now broken. (If you rely on tests, you have to make sure you have tests in every invoking module, rather than just the module you are actually changing. [Ignoring mocking issues...])
The self-documenting part is the strongest argument I've heard, but writing real documentation and real tests still seems better.
TypeScript doesn't help with immutability at all. For that, you should look at something like PureScript. (Or GHCJS, or any other compile-to-JS languages that support immutability.) TypeScript assumes you are sticking with JavaScript's semantics, mutability and all. If you want immutability, you still need to use "Object.freeze()". Variable mutation is not something picked up by types alone.
There are other languages that introduce "dependent types", which can actually include values in their type signatures, which might be able to specify something like you want; i.e. "a function that accepts an argument of type 'a' which derives a number, and returns the same type 'a', but the returned value must be equal to the input value plus 10".
Documentation is nice... or so I've heard! Speaking for myself, developers are lazy and just want to write code. Why not make them document the code as they write it? Plus, external documentation (even as JavaDoc-style comments) always falls out of date with the code.
I think better than tests, is to enforce "design by contract" - assert your preconditions and postconditions on every function. This inlines your assumptions and behaviour verification with the actual code to be run. (If performance is a concern, you can implement this in a way that allows you to disable assertions.)
In general, the more guarantees you can enforce at compile time (such as only accepting certain types of values, or that input values are never mutated in place), the less need there is for having twice as much testing code as application code.
OTOH with unit tests, you've got to find/fix not only your app code but the test code as well. You may forget some things because you overlook a function and its tests. And of course your test coverage is likely not 100% so things may slip through.
So in addition to "refactoring", I'd add "guarantees": literally one line of code gives you 100% coverage.
PLUS autocomplete. JS editors have gotten better, but TS blows them out of the water. Importa new library, and stuff almost codes itself.
Mutability is somewhat orthogonal to the discussion. You can write things immutably or immutably in typed or untyped languages. I prefer mostly immutable too. But, going with it, I'd been a Clojure fan for a couple years, where immutability and dynamic types are both forefront. After rewriting an app in F#, and then performing a major refactor, this is what I had to say about it. https://disqus.com/home/discussion/owenrh/the_beauty_of_cloj... tldr: refactoring is much easier with types backing you up.
n.b. There's no reason you have to choose. You can have all three. Immutability, tests, and types. I do. Though if I had to choose, I'd pick types first. I'm a disciplined enough coder that immutability guarantees aren't that necessary, and if I've got types and discipline, then tests usually are unnecessary.
Certainly type checking can prove that it's the right type, but in my experience knowing that it's the right type but the wrong data is useless, I care if it's the exact data it's supposed to be.
How does type checking protect you (or someone else) from inadvertently mutating a value without changing it type and causing problems later? It's perfectly possible to do this without having any checks for class/instance throw a warning, unless there's something I'm missing.
Sure, but that's what happens in every other language e.g. C's type system will enforce certain properties for you but when you compile to machine code the machine code is not checking those properties at runtime. If you know the compiler is correct you don't have to worry.
Nobody is saying you should stop writing tests but a good type system will mean you have to write less tests because the type system will do some of the checking for you in a more robust way.
A: C's type system is very useful for data validation.
Please read at least one sentence before writing a response next time.
The concrete advantage of TS is the same as the concrete advantage of any strongly typed language - types let you do static validation, and unlike tests, they allow for that validation to be provably complete (of course, the domain of constraints that can be so validated depends on the power of the type system in question).
It seems like using type checking as a reason to write fewer tests actually just creates nastier bugs in areas where your coverage is based on type validation.
What am I missing about checking type mutation !== mutation being a problem?
What is the advantage of strong types if your data structures are immutable? Is there any? I'm genuinely trying to understand if TS is just for cases where uncontrolled mutation is the norm and test coverage is poor.
That said, immutability by itself won't save you from passing the wrong shape of immutable object into a function, just as validating the shape of the argument passed and the argument expected against each other won't help if there are other assumptions being made by the code (such as that the object cannot be mutated). As one of the parent comments pointed out, types are proofs. Not everything can be proved to a compiler, but (assuming a bug-free compiler) everything which can be proved to a compiler does not need any additional tests. Things which cannot be proved to a compiler will need tests, of course.
Can you give an example? I'm not following what you mean. Why would strong types be less useful if your data structures are immutable? OCaml mostly uses immutable data structures for example but its strong type system is fundamental to why it can be used to write such robust code.
Typescript has a stronger type system than JavaScript so Typescript can therefore capture more program properties statically compared to JavaScript whether you're using mutable data structures or not.
Entirely possible people are comparing apples to oranges here. My experience is all based on structured data; e.g. User, Address, Order, Product, etc, and the relations between them. Data is generally static throughout the duration of the call; e.g. CRUD app.
It sounds like your experience is more with raw numbers and collections, and higher rates of entropy. In that case, I can see how types wouldn't add much value, and tests would be far more essential.
Languages with strong enough type systems can statically guarantee that data is structured correctly at runtime. Stronger structural requirements require stronger type systems. Algebraic data types are sufficient for many common static structuring guarantees.
Thankfully this rarely happens.
Check out some typescript tooling videos, this one was shown at an angular 2 conference but you don't need to know any angular to watch it:
Types are not a substitute for tests, but they eliminate an entire class of bugs that are screened out at compile time. That way you can focus your tests on functionality and not silly mistakes.