I'm using it now at my work and I'm surprised how much info I'm getting compared to whatever JS is throwing at me.
I'm using it now at my work and I'm surprised how much info I'm getting compared to whatever JS is throwing at me.
There were many such systems in the past. CoffeeScript was a big improvement over 2010 JavaScript. Before that was GWT, which was sooo much nicer than 2006-era JavaScript. I could list a bunch of others. If you used any of them, you're now stuck with a legacy system(+), and you'll find fewer and fewer people able to read/write your code, and otherwise.
I'm also not a big fan of static typing. And if you do static typing, you should at least do it properly, which is not what TypeScript does.
Types should be things like "This is an integer between 0 and 100," "Meters/second," or similar. We've known this for a long time. Ada is now 40 years old and was mandated for military work since if you added feet and inches without a conversion, that was a problem, and that resulted in more robust systems (TypeScript would make them both of type "number" and never catch the error). If you tried to have 120% of your fuel tank full, you'd violate an assertion as well. Those were turned on in dev and in your test suite (but generally turned off in your deployment system, which was running on a 33MHz CPU if you were lucky).
C++ templates let you define things like "This is a list of lengths, in meters." Duck types languages like Python won't do this statically, but it's easy enough to do dynamically; while you won't catch errors at compile-time, at run-time, you'll get a clear exception.
(+) Standard disclaimers apply.
I'm curious though why you are not a fan of static typing? I feel like if you have a language with inference it's pretty good. I mean you could imagine python with type inference and you could essentially write anything in it you can today (baring stuff like heterogenous data structures) without specifying the type manually.
(lambda (x) (* x (+ 3 x)))
A half-decade later, someone can take that code, pass a pair of specially-crafted objects to it to introspect what it does, and take a symbolic derivative, pretty-print that derivative with LaTeX, combine it with a few other pieces, and compile it into native code (and yes, that does happen).
But a lot of my code is generic in a way where types get in the way.
(2) If I do have typing, the type should usually be specified manually, but it should have semantic meaning, not be based on inferring whatever types my programming language happens to have built-in. What's important isn't that something is an integer, but that it's a count-of-apples. I should be able to compare a count-of-apples to a count-of-oranges without first converting them to a count-of-fruit.
This is especially important for numerical code, and especially in education. A lot of Scratch tutorials have little kids add a velocity to a position, without first multiplying it by a time. That leads to deep-rooted misconceptions.
I don't mind type inference if it's designed to give me feedback. I've seen a few systems (designed for students) which do things like take JavaScript:
let x=5;
let y="hello";
let z=x+y;
And flag that in the IDE. That's kinda nice. And obviously, many systems do optimization at runtime based on inferred types. That's not a problem either.
So there's a fuzzy layer in between. But the discussion was about TypeScript, not that fuzzy layer.
The second example with count of apples and oranges is bad. Those are unitless and a thus Just an natural should be fine for them. Numerical code can get messy but that's exactly were the power of static typing is the best. You can have dimensions and units in a type system and you get dimensional homogeneity correctness for free.
re: type inference
Inferential type systems fall into a fuzzy zone between statically and dynamically typed. For example, many JITs for dynamically typed languages do type inference, and generate compiled code optimized to the types actually being used. That's a very obviously good idea. And many linters look at inferred types as well to do compile-time checks. That's also an obviously good idea. But I wouldn't call a JavaScript JIT a statically-typed system (or Python code that's been through Pylint).
re: Apples versus oranges
They're not unitless. In the first case, the units are apples. In the second case, they're oranges. You shouldn't compare or add those types. If I do have a type system, that's something I should be able to specify.
Not to mention implicit conversions, if I want them (12 inches + 1 foot = 24 inches).
Take a look at the Haskell units library. That can do the things you illustrate.
Ruby, Python, Erlang/Elixir, Lisp/Clojure, etc, all doing just fine without it. (Some, with some lightweight annotative-oriented typing for a subset of projects.)
The best counterargument to static typing I can articulate is that in general, the only way to be sure software works is to run it. And so, ultimately you need to backstop your software with testing, QA, and observability infrastructure to actually execute it and validate it. At that point, the question is what marginal gains remain from catching a subset of those problems at complile time, also incorporating a potential a false sense of security which may lead to less actual execution validation, and more user-facing issues. The other potential tradeoff of static typing is if the software design and architecture changes for better or worse, or if the velocity of shipping changes changes. I believe there isn't yet any hard evidence of which way this tilts in either case.
There's also a lot of innovation in the area of validation-by-execution as well, and so I think that will result in continual debate: state-of-the-art static typing may benefit compared to yesterday's testing/fuzzing tools, but may not compared to state-of-the-art testing/fuzzing tools, for example.
We're doing fine without, but it's definitely a sore point and I'm one of those developers who has become more and more fond of static typing.
- Small codebase. Fine, use both.
- Medium codebase. Fine, use both.
- Large codebase. You're definitely going to have some typing bugs with dynamically typed languages.
- Humongous codebase. You're definitely going to have many typing bugs with dynamically typed languages.
This should enable you to catch most errors immediately during development.
What you need is something that can help you test more paths, and static type checking checks all paths. Further, they can check all paths without writing any tests (you should still have tests, but you need fewer with a static type system to keep your bug rate the same). Moreover, static type checkers catch errors almost immediately, so you get feedback about your errors sooner rather and more frequently, so you spend less time investing in code that will have to be rewritten. Lastly, they put rails on the code—it’s harder to write bad code including gratuitously magical or abstract code, so your coworkers aren’t writing as much bas code that you have to interface with. If you find static type systems to be frustrating, you’re probably the problematic coworker. :)
This is not a Python problem, per se. It is a developer problem. It is a human problem.
However, with your own code and libraries, you can enforce your own rigor. Keep your functions smaller, test all the inputs, and validate all the outputs. Although you may end up with smaller production code, but more unit tests.
The unit tests are good, as they give you evidence that you did cover those corner cases.
However, the good thing, is that if the functions are smaller, and well unit tested, then it becomes rare that you have to modify it at a later date. And especially if you think through all the different angles, and try to future proof it. Now you have the ability to do functional composition. Just keep chaining the outputs of one function into another.
the question is if those programs were written in statically typed languages, how the world would differ, in-full. for example, they may have been built faster or sloer. or they may have better or worse test coverage, affecting severe defect count overall. or you may have had to hire a different team, with tradeoffs. hard to say.
Use as many hooks and context as possible and keep your redux store as simple as you possibly can because if you end up with a deprecated state management library (redux-thunk, sagas, easy-peasy) replacing that is like pulling gum out of hair.
If you add up the time spent working around typescript errors when dealing with state, it's possible for it to be more time than you need to rewrite the entire app.
For backend work then yes, I'd say doing a singleton or an abstract class in typescript makes more sense in hardening your API code and even making it more readable too.
Also, I don't know why you're referring to thunks and sagas as "deprecated". Thunks are the recommended default approach for async logic in Redux [2], and we include them out of the box in our Redux Toolkit package [3]. While most apps don't need sagas, they're a great power tool to have in your toolbox. Neither of those is in any way "deprecated".
[0] https://blog.isquaredsoftware.com/2019/11/blogged-answers-le...
[1] https://redux.js.org/style-guide/style-guide#use-static-typi...
[2] https://redux.js.org/style-guide/style-guide#use-thunks-for-...
[3] https://blog.isquaredsoftware.com/2020/02/blogged-answers-wh...
I should have put that better, the libraries themselves are not deprecated, their dependencies can be.
While this negligence problem would be on the fault of the dev team and management for not doing regular package updates it is an all too common issue in many companies.
What I am saying is that bad typescript is harder to fix than bad javascript because type errors compound the problems of convoluted code.
The additional compilation complexity is not worth the small benefits of TypeScript, in my opinion. An even better language, though? I'm in.
I still prefer using other languages when I can (back-end), but hypothetically, if I had to choose between Ruby and Typescript I'd actually go for the latter. And I like Ruby!