I never really got into F# or Haskell (more than some tutorials) so can't really comment on the type safety part.
I never really got into F# or Haskell (more than some tutorials) so can't really comment on the type safety part.
In open-world domains, like business information systems, static typing is often an obstacle to fast adaptation.
Whereas immutability provides value in every domain, unless the performance requirement cannot be met.
The application domain is not relevant because you rarely know the domain up-front. Even if the domain is fully known by business stakeholders, it's not known by the application developers, and those domains can be vast. Application development is a constant process of learning the domain and of extending the existing functionality.
This is why all the talk about how LLMs are going to make it possible to replace programmers with people using prompts in English doesn't make much sense. Because the act of programming is primarily one of learning and translating requirements that are initially confusing and context dependent into a precise language. Programming is less about making the computer dance, and more about learning and clarifying requirements.
Static typing helps with refactoring, A LOT!
So when your understanding of the domain changes, YOU WANT static typing because you want to safely change already existing code. You want static typing precisely because it gives you “fast adaptation”.
It's the same argument for why someone would pick Clojure over other dynamic languages. Clojure gives you some guarantees due to the pervasive use of immutability, such that it gives you a clearer view of the API's contract and how you can change it. But statically typed FP goes even further.
I've been involved in projects using dynamic typing (PHP, Perl, Ruby, Python) and with no exception, the code became a mess due to the constant evolution. This is one reason for why we preferred an architecture of microservices because it forces you to think of clear boundaries between services, and then you can just throw away or rebuild services from scratch. Large monoliths are much more feasible in statically typed languages, due to the ability for refactoring. And no, while unit testing is always required, IMO, it isn't the same thing.
I, for one, prefer more static typing, rather than less. I prefer Scala, OCaml, F#, or Rust. And I've seen some difficult refactorings accomplished in Scala due to its expressive type system, although I can understand why it can be a turnoff.
The downside of having more static typing is a bigger learning curve, so you end up sacrificing horizontal scaling of software development (hiring juniors fast) over vertical scaling (doing more with fewer, more senior people).
Another downside is many times a slower compiler, which changes how you work. Once the code compiles, it may be correct, but then again, you end up doing less interactive development, so you work more in the abstract, instead of interactively playing with the code. I.e., Python's `pdb.set_trace()` is rarely available in static languages. I've always found this difference between dynamic and static languages quite interesting.
Why, then, don't you prefer languages with more static typing? Scala, Ocaml, F#, and Rust are middle of the road at best.
It seems you're echoing that the pragmatic choice for a business application is to stick to typing basics (within some margin of what is considered basic).
Haskell, for example, is harder to pick, and I wouldn't pick Idris even if I founded my own company.
It's supposed to be 'brittle', in the sense that the compiler verifies your code and if the logic changes, the compiler complains. Everything else is a bug.
> But, if you have business people tell you, you have to pass some additional information through your system without doing anything to it, and you answer, I have to refactor all my type definitions, then you have some explaining to do.
And the explanation is that this is software development, there is no "without doing anything to it". If there's a new requirement I need to adapt the code - take it or leave it, I'm not a wizard. And yes, I have done that already in some form and it was almost always received properly. 'Business people' sometimes have no understanding of software development.
The logic "code changes -> only dynamic typing" isn't valid, in my opinion.
The data model did change though. Adding an extra field, even if you don't use it, changes the shape of your data.
Ultimately your data is going to be typed with or without your approval. It's unavoidable, because eventually the data needs to be bits on a disk or on the wire. It's just a matter of how aware of it you want to be.
If you can reasonably keep the shape, and all possible shapes, in your head then fine. But I think you'll find this becomes less feasible as systems grow, and even less feasible in a corporate environment when teams come and go.
In contrast, Javascript is weakly typed. Yet, Clojurescript, which compiles to JS, retains Clojure's strong typing principles even in the Javascript (weakly typed) runtime. That (to certain degree) provides benefits that even Typescript cannot.
Typescript's type checking is limited to the program's boundaries and often doesn't cover third-party libraries or runtime values. Typescript relies on static type analysis and type inference to catch type errors during development. Once the code is compiled to JS, all type information is erased, and the JS engine treats all values as dynamic and untyped.
Clojurescript uses type inference, runtime checks, immutable data, and dispatch mechanisms, optimized by its compiler, to achieve strong typing for the code running in the JS engine.