I agree, but I think it’s a common misconception. Having used many languages, both for very large and small projects, languages with compile time static type checking and run time dynamic type checking, I find it’s easy to rely too much on static type checking. I think what happens for a lot of programmers is that static type checking uncovers a lot of basic bugs and typos, so they feel like it has a major advantage over dynamic. The problem though is that it’s a false sense of security because you’ll always end up with complex logic bugs that no compile system is ever going to catch, so you end up needing test suites anyway.
Static type checking does have some advantages, it can ease some of the low level testing burden. For instance, using a tool like Dialyxir (dialyzer) is helpful in elixir to give you that extra security of static type checking.
For me it’s always ended up being that I’m more productive with dynamically typed languages and the flexibility they offer.
This may sound like a dismissal, and in a way it is, but: get a better type system, and really understand it. The kinds of logic problems that you describe can be greatly limited—without wasting one’s time—with a more expressive type system. This is one of the reasons I lean so heavily on TypeScript today; I can express my data in the forms it actually should be expressed in, pretty easily, and use that to constrain the logic involved. I can then use features like assert guards (“if this method returns then the listed argument must be type T”) and type guards (if returns at all, the single passed argument must be type T”) to strengthen my understanding of the processes resulting in that data. I can then use a much thinner and much faster to write set of unit tests to proof cross-cutting concerns and integration tests to prove the end-to-end case.
It’s not a false sense of security; it’s automation to cut out the stupid bottom 75% or so of boring-to-write, disastrous-to-screw-up test garbage that we all have better things to do with our time than duct-tape together.
(Also, Dialyzer is pretty bad by comparison to a real type checker. At best it’s spotty and inconsistent, it fails open in weird ways, and libraries rarely implement it. Elixir has an argument for “the least worst dynamically-typed language in common use” but Dialyzer ain’t it.)
At the end of the day, it’s about can you deliver that project on time and with limited bugs. Everyone’s experience will be different, so maybe accept that different programmers work better with different systems.
I can tell you with 20 years of programming experience that static type systems don’t make me more efficient... and I’ve used them all.
And just because I can see the reply coming, I’ve worked with programmers using static type checking that have turned out horrible bug ridden code. It’s just not a cure all.
So can we please accept each other’s choices about type systems. “Can’t we all just get along”
Ultimately if it matters to you, then choose what works. But don't assume or try and tell everyone it makes one language definitely better than the other, because things aren’t that simple.
Put frankly: I’m not going to “accept” that because I’ve watched programmers with twenty years of experience (woe betide me and my mere thirteen!) tank projects through this kind of arrogance. And it is arrogance, to dress up the limitations of a meatbag as a positive against the tireless specificity of the machines.
Then those meatbags realize that they have to refactor their code and the world ends.
The computer is infinitely better at crossing the t’s and dotting the i’s than you are. Or I am. Relying on conscientiousness when you have tools to measure and correct is a sucker’s bet. And I will go so far to say that with the wonderful tools available to us in 2020, dynamically typed languages put “on time” and “limited bugs” in an unnecessary tension.
Wow... talk about arrogance! I think you’re unclear about the definition of that word. I’m suggesting that developers have choice in what works for a given project. It’s arrogance to think only your choice is correct.
You’re the type of programmer that tanks projects because of that arrogance and an inability to work with other “meatbags”. Maybe for you it’s all about code perfection, but for the rest of humanity it’s about making a product that works, solves a problem, makes the world a better place, or earns a profit.
Too bad my suckers bet has paid off with success and money. Best of luck with your attitude in life.
Guess these Github meatbags have made a suckers bet on a dynamic language. No way they could be efficient or successful maintaining or refactoring a project of that scale. (Yes that’s sarcasm for the sarcastically impaired)
Put frankly: you are talking out of your own experience and extrapolating onto the world. What a shocker that as a Ruby dev I have different views than you, who would have thunk...
> Static type checking does have some advantages, it can ease some of the low level testing burden. For instance, using a tool like Dialyxir (dialyzer) is helpful in elixir to give you that extra security of static type checking.
Dialyzer isn't an answer to static type checking. It barely works half the time and cannot be relied on to check things exhaustively. It's so bad that even libraries created by core contributors have type spec errors in them regularly. Why? Because they don't use it, because it's not reliable.
Both `Ecto` and `StreamData` had these issues several times and I can only assume you could find many more if you were even more invested in using larger parts of the eco-system.
This resonates with me as the first 10 years of career was using static typed languages, and saw that I and others in my circle were mostly using it as a very basic type of unit testing. This is helpful but at the cost of extra code and boilerplate. Some folks were incredulous that I shifted to languages like JavaScript and was fine with the weak typing, but tbh I'm a much better at unit testing than in the past. I've never really experienced the "omg no types" reaction that many people do. Different strokes I suppose.
I think there's a eye-poppingly large part of the community won't write tests unless they're dragged into it kicking and screaming and it gives them to "not have to write tests". I get it. Tests can feel like you're writing your code twice, and, they can add a bit of drag when it's time for refactor (a good thing IMO). If I am permitted to be cynical, a testing culture discourages "think about it really hard and then pull a solution out of your nether regions"-driven programming, which is a culture that is promoted by hiring practices at... Certain well known companies, which, in turn, is cargo-culted by startups.
Also most PLs/frameworks have very bad testing ergonomics, so that doesn't help.
You can't publically admit to "hating tests" because that has very bad optics. So, being a static typing zealot is the next best thing (after all, it will protect you from some classes of errors that are covered by testing). You pay for not writing tests in a bit of boilerplate, but boilerplate doesn't feel like you're writing code twice.
I don't agree with this at all. I am about as hardcore a static-typing advocate as you'll find--and I love tests. I write tests before I write code whenever I can do so.
But I also like writing tests to prove logic and rules, and not tests for data validation--because computers are incredibly good at nitpicky stupid shit, they can and should and must do it for me, and I have more important things to care about than "did they pass an int when I wanted a string?".
Modern statically-typed languages elide a great deal of that boilerplate you refer to, while still providing protection against foolish errors. And given that the rest of it acts as a runtime-introspective set of documentation for what you're doing, the rest of what you're describing as "boilerplate" provides other value as well. (The web framework I use, for example, uses your declared data types both as HTTP validation and to generate OpenAPI documentation for both humans and other computers. In a dynamically-typed language I'd still need to define these things as JSON Schema or the like--which is what happens under the hood here, it just actually provides for me dev-time and not just runtime benefits.)
The sorts of errors I'm talking about that typing will never help you with are object ownership (in non-rust, non-gc languages), use-after-free (ok rust helps with that too), array bounds-checking, use of optionals, incorrectly labelling functions (fn add(a, b) = a - b), race conditions, non-pointer resource cleanup (e.g. file descriptors, connection objects), paralellism errors (noncommutativity of operations), mutex deadlocks.
Basically all of these things (or their rough equivalent) are easily testable in Elixir.
Your point is taken in the large (I wrote a zig NIF library that reads zig types and automatically generates the code that performs the data deserialization from erlang terms), but in the specific case you noted, if you're using type annotations to create your OpenAPI spec, you're doing it backwards. Best practice is to OpenAPI spec to generate endpoints that you code for. (I'm aware that phoenix-openapi does not do this correctly)
Also, I want to point out that I wrote a generalization, not an absolute statement about typing zealots. I think you're rare. I like types and even wrote an (unpublished) typing library for elixir (proof: https://github.com/ityonemo/typed_headers) that engaged during tests, with the intent to make it compile time checks. But almost every typing zealot I have worked with absolutely hated tests.
TypeScript, just as an example, does help with a lot of those, though. Object ownership is a lot less of a thing when you hand `DeepReadonly<T>` to things outside of your bounded context. Use of optional constructs is also de rigueur in TypeScript (whether or not you're using an option type or `undefined`/`null`) and the compiler will happily yell at you about it.
The rest--yeah sure, but you can write tests for those things in anything. What you must test is then only a subset of the things that yeah, you do really have to bust your ass to test in an Elixir or a Ruby or a Python, and then it really is boilerplate--just less useful boilerplate overall.
> Best practice is to OpenAPI spec to generate endpoints that you code for.
I disagree with that. Every project where I've ever done that has immediately shat down its leg when the spec of an endpoint changed because refactoring from external sources is bad even in languages where refactoring isn't a disaster from the word 'go'.
IMO, the code is the canonical source of functionality; the spec should reflect it. It's why my CI system for my current project builds new releases of libraries for major languages are pegged to the version of the OAS3 spec being emitted. If you want to go off-road and use a language I don't actively look at, you can still do that, and the semver of the spec (which remains a manual problem in either case, though you could probably get clever enough to make a good guess as to whether it should be major/minor/patch) will tell you as a consumer when you need to upgrade.
A semantically versioned spec is required whether or not you write the spec first or the application first
> But almost every typing zealot I have worked with absolutely hated tests.
Slices both ways pretty easy, tbh. Almost every dynamic-typing pusher I've worked with was an active danger to a project with more than one committer--the best and most effective developers on such projects have always been folks with the requisite fear and acknowledgement of their own fallibility and the absolute danger that comes with using dynamically-typed languages for anything you care about; the starry-eyed Ruby idealists (I had that phase too) are just going to be too clever by half and then you get to eat the resultant shit later.
Static type checking does have some advantages, it can ease some of the low level testing burden. For instance, using a tool like Dialyxir (dialyzer) is helpful in elixir to give you that extra security of static type checking.
For me it’s always ended up being that I’m more productive with dynamically typed languages and the flexibility they offer.