All strong types do is make it explicit at compile time (and with static analysis), instead of leaving it to be discovered in tests or at runtime.
This is why I'll use Python for small one-off scripts but anything that will be in regular use I prefer to do in Java. Even though I have many more years of experience with Python.
- Don’t have big projects.
- Have lots of unit tests to cover some of what a C# or Java compiler would have caught through static analysis.
- Don’t even consider doing certain kinds of refactoring (which would be trivial on a strongly typed language) because they have no way of knowing what will break.
Of course it’s not impossible to have a good JavaScript code base, it’s just much harder to have a very large one where it’s still economically feasible to make significant changes to it without having to resort to a rewrite or needing to write tests which would be covered by the compiler in a strongly typed language.
Too bad you don't know me so you're left to ad-hominem attacks on my expertise.
>without having to resort to a rewrite.
Glad you get to move the goalposts anywhere you want to justify any comment you make. You haven't worked on every codebase that ever existed, even though it sounds like you think you have.
In many cases, yes, it is worth a rewrite instead of trying to shoehorn something that exists, only because it exists but is the wrong solution for the new requirements. We're not going to get into the weeds of every kind of refactor in every kind of codebase here on HN, so instead you can move the goalposts and I'll just stop replying here. We can agree to disagree and call it a day.
I also haven't seen every codebase in existence, but I have seen enough "it's only going to take a year" rewrites that absolutely should have been refactorings and so I remain rather skeptical of the claim that rewrites make sense in "many cases".
If dynamically typed languages only worked well in absolutely pristine codebases that never saw any hacks or poorly thought out solutions, I'm not sure they'd be actually useful in most real-life settings. Of course, people refactor all the time in dynamically typed languages as well.
Strong and weak are sort of coherent as a spectrum, but they aren't a typology, and they do not in any sense or in any case reduce to static vs. dynamic types. Conflating them is not useful: I can make a good case that Julia's type system is stronger than C's, but Julia is dynamically typed and C is statically typed.
There's no reason to keep doing this. It's a malapropism, we could just.. not say that. Especially in a thread which is specifically about types.
Given the fantastic iteration speed that the Web platform had (and still does to some extent) I didn't really miss a compiler for catching errors. The main improvement is that IDEs find it easier to support big refactoring.
I also worked on a mid sized scala codebase a little later on. Scala is probably better now than it was then, but despite (perhaps because of?) the cleverer type system, everything was so slow that it actually took longer for many bugs to be highlighted by the compiler than they would have been found by hitting f5 in a browser window with a good js codebase. That was when I realised that as a developer I care a lot about when an error is highlighted in wall clock time and not at all about which compiler phase it was discovered in.
I found working with type inference being its own skill. If you know how to place type annotations well, the ripple effect only affects one or two callsites and then the type inference just continues to infer what you meant in the subsequent code. Though we may be approaching the structuring of the code differently so YMMV. But I haven't had issues even with complicated member constraints which replicate dependent typing.
F# is derived from OCaml, and they both support interface files–in the case of F#, with the .fsi extension–which is designed to solve exactly this problem. After settling on the surface area of your module, you nail it down by writing an interface file with the exact types you want to enforce. The compiler then uses this to check the usage of the module by its consumers, and provides much more accurate type errors.
It works really well.
After a while, I found myself adding back types in a decent number of places to "anchor" the type inference, indicating that a certain type/signature is fixed and a change should be carefully considered.
I still don't know how folks deal with these kinds of changes in weakly typed languages without always allowing bugs to pour into their code over time. But I do love the "move fast" and low boiler-plate aspects of "typeless" coding.