How do you express invariants in dynamic languages? (In a way that doesn’t require test coverage to check those invariants?) I thought the only way would be to write some assertions in the prod code, and then write the unit tests to cover them… but you seem to be implying that you can “do” invariants in dynamic languages in a way that doesn’t require testing.
They obviously work best when "combined with test coverage" but then again, I dont consider test coverage to be "optional" in any language. This idea that "if it compiles it works" that some haskellers seem to believe, for instance, is complete horseshit.
If somebody argues that static typing works great because without tests it catches more bugs than dynamic typing does when you also dont write tests, it's notionally true, yes, but theyre telling you more about themselves than they are about types. Namely that they're not in the habit of writing tests.
If you've got enough of the right kind of testing coverage to validate an app written in a statically typed language you will have enough to trigger invariants in a dynamically typed langauge.
I have successfully had invariants usefully triggered in prod and by manual testers in big balls of mud without test coverage (failing fast isnt just for test environments) but I considered that to be a form of emergency programming CPR and damage limitation that isnt a substitute for fixing your testing "diet".
Agreed; "if it compiles it works" tends to tilt too far in the other direction.
(... I once had a professor who believed that phrase as nearly a point of religion, and his project work was maddening. No documentation on any functions either, because "the types are self-documenting." Then you'd get hit with a 'division' function that takes (number,number) but he put the denominator as the first argument. Ugh.)
I'm not an "expert" at dynamic languages, but I've written small microservices in Clojure and Elixir, data crunching code in Python, and your typical junk in PHP and JavaScript.
Clojure does have spec (https://clojure.org/about/spec) which is pretty cool and declarative. And, of course, your IDE can help you when writing code that obviously violates a spec declaration. But that's not fundamentally different from an IDE parsing the type-hints of Python, JavaScript, or PHP. It is more powerful than the aforementioned type-hints, though.
At the end of the day, it blurs the line between static typing and dynamic typing. IMO it should be considered static typing because it serves that same purpose, works at "write time" to help the programmers, and can probably absolve you of writing your own type-checking tests.
In some sense, it is all just asserts. Even statically typed languages have unsafe type casting, which can trigger an "assert" at runtime.