Both Java and C# tend to rely heavily on frameworks such as Spring to workaround issues with the expressivity of the languages. This causes problems when one needs two frameworks (they don't in general compose). In Haskell, HKTs allow one to write polymorphic programs that are parametric with respect to certain behaviours and dependencies, no dependency injection framework needed.
Please don't judge Haskell using Scala and scalaz.
I just don't understand what is so horrible and inexpressive about a static initialization block.
The only possible purpose I see to Spring is if for some reason you really need to be able to change how your dependencies are injected at runtime. (90% of Spring apologists point to this, and 99% of them never use it in practice.) Even then, I don't see how a Spring XML config file (which I have seen run to 4000+ lines, to my horror) is better than just reading some settings out of a properties file to pick an implementation in your static initializer.
Java's static initialiser blocks are too dangerous whenever one has threads.
But I think I get your general point: things like 'Control.Concurrent.Async' ('async'/'await') and 'Control.Monad.Coroutine' ('yield') are libraries that implement some and very generic type classes: 'Functor', 'Applicative', 'Monad'. This then lets you use features that are generic over those type classes ('do' syntax, 'fmap', ...).
It's been many years since I had a proper look at Haskell. Maybe it just takes more practice than I had back then to fully "get it". But I still don't see those abstractions being that useful in everyday programming. They seem to have huge potential for hard to follow code as you need to mentally unpack and remember more layers of abstraction, and the gain is not clear to me. Even the features that have trickled down to C# are not _that_ crucial I feel. The way mainstream languages pick the most useful use cases of those abstractions seems pretty OK to me.
(Also, macros and compiler plugins are another interesting avenue towards very powerful abstractions, with a different set of problems.)
As for Spring and dependency injection, I don't follow how HKTs would help there. Could you give an example? Aren't DI frameworks mostly about looking things up with reflection magic to automate, and arguably just obfuscate, the task of wiring things up in 'main'?
That's the beauty of abstraction without side effects, you don't need to unpack anything. If you know what the inputs are and the outputs are, you don't need to know how it works or what type classes are even used to transform certain things.
People use sequence
all the time in Scala, not realizing it's only able to be implemented with HKTs of Applicative and Traverse. FYI, sequence flips a list of Futures to a Future of List, or a vector of Trys to a Try of Vector, etc.
I don't buy your point about not needing to unpack side-effectless code, however. There are _always_ reasons to dig into code, be it bugs, surprising edge cases, poor documentation, insufficient performance, or even just curiosity. And those high-level abstractions tend to be visible in module interfaces too. I remember some Haskell libraries being very hard to figure out how to use if you didn't know your category theory :)
What I meant by "polymorphic programs" as an alternate to DI, is something like this:
doStuff :: HasLogger m => Input -> m Output
The effectful function "doStuff" above is polymorphic with respect to which logging implementation is used, it could even be one that uses IO. All made possible with HKTs.
Your DI example seems to be an example of my earlier point about "emulating things you could do with side-effects". No HKTs are needed when you just pass an impure side-effectful Logger object. Or, as discussed in another subthread, you could do side-effect management with Rust-style uniqueness typing, which results in a less elegant but arguably easier to use type system. It's debatable, but it seems people struggle less with the borrow checker than with advanced Haskell.