I like Haskell, I write Haskell at my day job (and did so at my previous day job), and I help maintain some of the community build infrastructure so I’m familiar with a large-ish graph of the Haskell ecosystem and how things fit together.[0]
I don’t really think Haskell is _meaningfully_ superior than other languages at the things that OP is talking about.
Refactoring Haskell _in the small_[1] is much nicer than many other languages, I don’t disagree on that point. Despite this, Haskell applications are _just as susceptible_ to the failures of software architecture that bind components of software together as other languages are.
In some cases I would even suggest that combining two Haskell applications can be _more_ fraught than in other languages, as the language community doesn’t have much in the way of agreed-upon design patterns that provide common idioms that can be used to enmesh them cleanly.
[0] I’m mostly belaboring these points to establish that I’m not talking out of my ass, and that I’ve at least got some practical experience to back up my points.
[1] This is to say when one refractors individual functions collections of interlocking abstraction
> Despite this, Haskell applications are _just as susceptible_ to the failures of software architecture that bind components of software together as other languages are.
I think it's more complicated than this. Yes, you can push poorly-architected Haskell to production & be in a rough spot. However, my experience says that even the gnarliest Haskell is easier to improve than any other language.
Because of the types, purity, etc, I find that it's much easier to zoom around a codebase without tracing every point in between. I can typically make one small change to "crack things open" [1], follow GHC's guidance, and then go from there. I've been able to take multiple large Haskell projects that other engineers deemed unfixable (to the point where there were talks of rewrites) & just fix them mechanically and have them live & improve continuously for years to come.
The big thing with Haskell IME is you don't really need to have design patterns that everyone follows. I don't freak out when I see multiple different idioms used in the same codebase because idgaf about folk programming aesthetic. If an idiom is used, I follow it. It's all mechanical. I barely use my brain when coding professionally in Haskell. I save it all for the higher-level work. Wish I could say that about professionally programming in other languages of equal experience :/
So while it's just as susceptible (because good vs bad software architecture is more a function of time & effort) it's also typically pretty braindead to fix.
[1] A favorite technique is to add a new case to a key datatype and have its body be Void. Then I just follow the pattern match errors & sprinkle in `absurd`. I now have a fork in the road that is actually a knowably a no-op at runtime.
WAI is a great example on the sort of compat/interop interfaces that are more common and easier to rollout in Haskell than in non-(typed functional) languages.
What things make WAI easier than the equivalent abstraction in $other_languages?
(Not going for a "gotcha" tone here, genuinely interested as a person who has tried and failed a few times to write "useful" software in Haskell. Would like to get there one day).
...which is to say that WAI applications are “just” functions that accept an incoming request and a callback that turns that request into a response object (which may emit side effects), and which return a respond object (which may emit side effects).
This leads to a very nice definition for middleware as the following type alias:
type Middleware =
Application ->
Application
...which says that any WAI middleware is just a function that turns one application into another.
This means that WAI applications have a nice and easy to understand top-level interface, and that complex chains of WAI middleware can be built up by chaining smaller middlewares together.
The potential benefit here (over other language frameworks) is that the “grammar” being used to describe applications and middleware is the same “grammar” that’s used in most other Haskell applications
(i.e. function composition). Ideally, this should make it more easily understandable to a Haskell practitioner who might not be intimately familiar with the framework at first glance.
You've got far more Haskell experience than I do, but I have done some pretty heavy refactoring on large java codebases. The process always seemed to be, tease out some interfaces and switch the implementation of those interfaces. Over and over and over. I could lean on javac and tests but some knots are hard to untangle and take a long time.
I believe you that the in the large it's still hard. It seems so much more pleasant day to day untangling that big ball of string with Haskell rather than Java.
I don’t really think Haskell is _meaningfully_ superior than other languages at the things that OP is talking about.
Refactoring Haskell _in the small_[1] is much nicer than many other languages, I don’t disagree on that point. Despite this, Haskell applications are _just as susceptible_ to the failures of software architecture that bind components of software together as other languages are.
In some cases I would even suggest that combining two Haskell applications can be _more_ fraught than in other languages, as the language community doesn’t have much in the way of agreed-upon design patterns that provide common idioms that can be used to enmesh them cleanly.
[0] I’m mostly belaboring these points to establish that I’m not talking out of my ass, and that I’ve at least got some practical experience to back up my points.
[1] This is to say when one refractors individual functions collections of interlocking abstraction