| I'm not convinced that you can follow all of these outside of Functional Programming. How can you "Make illegal states unrepresentable" with mutable state and sequences of mutations that cannot be enforced with the type system? How can you do "Errors as values" at a large scale without do-notation / monads? How can you do "Functional core, imperative shell" without the ability to create mini DSLs and interpreters in your language? |
Java (along with many other object-oriented languages) lets you create objects that are effectively immutable by declaring all fields private and not providing any property setters or other methods that would mutate the state.
Errors as values is one of the headline features of both Go and Rust, neither of which has do notation and monads.
Functional core, imperative shell is something I first learned in C#, but I don't think it was _really_ at significantly more of a disadvantage than most other languages. The only ones that let you really enforce "functional core, imperative shell" with strong language support are the pure functional languages. But people working in, say, Ocaml or Scala somehow still survive. And they do it using the same technique that people working in Java would: code review and discipline.
None of this is to say that language-level support to make it easier to stick to these principles is not valuable. But treating it as if it were a lost cause when you don't have those features in the language is the epitome of making the perfect the enemy of the good.