Other languages now also have quite sophisticated type systems, some of which can do specific checks beyond Haskell in certain areas (Rust's control flow analysis, TypeScript's ways to do "gradual" typing of JS), while Haskell can do more in general.
But only in Haskell can I look at a function's type and know it doesn't do any IO.
In development and production, this allows you to immediately skip over large amounts of code when debugging issues. Pure functions are a key reason why Haskell is good at correctness and maintainability.
I used to entertain myself embedding various languages into Haskell, mostly hardware description related.
Usually, one goes with a static parameterization of a monad. But [1] shows that it is possible to have a dynamic parameterization, where underlying types of a state can change.
This means that we can control state types and, borrowing from ST monad, we can control introduction of resources and their use. If certain free type variable is in state, we can use associated resource. Deletion of resource is a deletion of free variable from a state parameterization.
All this is from 2009 or so.
The borrow checker of Rust is a library in Haskell, if you patient enough.
The purity of Haskell made many these libraries possible, exactly because we can control different side effects.
Nim and D both have this feature. Among more popular languages, C++20 constexpr comes close: if you see a constexpr function, you know it's a "pure" function. Although there are still some things that can't be done in constexpr functions, the list is getting smaller and smaller with every new version.
Effect tagging in first order, monomorphic code is easy. Doing it in the higher order and polymorphic case is the tricky bit. Do Nim and D support that?
constexpr reduces general side effects. Can we reduce specific side effects?
For one very important example, consider software transactional memory. It has general side effects in the implementation behind the scenes, but inside transaction code they are not allowed.
I thought STM was dead. Seems to be minimal adoption within clojure and all the hardware which tried to do it has been disabled for being broken. What makes it a particularly important example?
The C++ plan of attack probably is more syntax though. Maybe parameterise constexpr, constexpr<input_only_io> or similar.
STM is very pervasively used in Haskell. It's usually among the first tools a production Haskeller would reach for when dealing with concurrency. It has died in other ecosystems mainly because it really shines when you have a lot of pure functions and a static type system, which is really up Haskell's alley. Any impure functions really spoil the soup.
Clojure's STM implementation is missing crucial combinators to combine different STM actions compared to Haskell (e.g. Haskell's `orElse`), which makes it much less useful. It's also more difficult to remember how to compose STM actions in a dynamically typed language, since you can't directly inspect the action at the REPL in the same way you would do with a simple data structure, which means those few Clojure codebases which use STM don't tend to use it pervasively, but instead confine it to a single place, because it gets too easy to make the equivalent of type errors in Haskell (but which are much more difficult to diagnose because they're not really type errors from the perspective of Clojure).
Or to put it another way, Haskell's STM implementation focuses on STM actions as first-class entities, which allows you to have all sorts of reusable actions scattered around your codebase or even stored in data structures (e.g. a list of actions). This is also why there are combinators for combining different STM actions (e.g. `orElse`) rather than just operators on transactional references.
This means you can build up a rich library of actions in different places throughout your codebase, then at the "very last moment" in your program you atomically combine different STM actions together (ensuring through atomicity that they all see a consistent view of the world).
Clojure's STM implementation on the other hand focuses exclusively on transactional references, which means it's difficult to build up a library of STM action "lego pieces" that you can reuse throughout your codebase. You can try to retrofit the same thing, but then you run into mismatches between different actions which is what I alluded to with type errors. This lack of composability is my view as to why STM has mostly withered in Clojure.
It is not dead in Haskell precisely because of controllable side effects. It is dead in some other languages precisely because of lack of controllable side effects. In .Net, for one example, STM execution was 4 times slower because of arbitrary modification of state [1].
Also, the rule of thumb is that you have to use STM, and it is mandatory, if there are more than one developer working on a project or if you have more than one module in your solo project.
My company codebase makes generous use of STM in Haskell. STM is the first concurrency solution I reach for in Haskell and I believe it's the idiomatic thing to do.
As for importance of example, it goes like this: double linked list is a first year programming exercise on a single processor; double inked list is master thesis on a multiprocessor if you use locks, etc; and it is again first year programming exercise if you use STM on a multiprocessor.
STM simplifies parallel execution of atomic consistent transactions significantly.
(I say "only in Haskell" disregarding some more sophisticated or specific languages such as Agda, Idris, Futhark, etc. but those are a lot more niche.)
Purity is Haskell's fundamental killer feature.
Other languages now also have quite sophisticated type systems, some of which can do specific checks beyond Haskell in certain areas (Rust's control flow analysis, TypeScript's ways to do "gradual" typing of JS), while Haskell can do more in general.
But only in Haskell can I look at a function's type and know it doesn't do any IO.
In development and production, this allows you to immediately skip over large amounts of code when debugging issues. Pure functions are a key reason why Haskell is good at correctness and maintainability.