To be fair I haven't really used it, only read the core of the guide, so I may be mistaken.
But I've done a bit of Clojure, and I've done a bit of Immutable.js, and particularly when you have a deeply-structured piece of data, "mutating" something a few levels down gets really ugly. Now, maybe something about Haskell Enlightenment obviates this case entirely in a way I'm not seeing. But I also remember Haskell seemingly forcing you to push all your imperative code up to the surface layer of your otherwise pure program, which sounds great unless you need to do really meaningful things that are by nature imperative.
"Now, maybe something about Haskell Enlightenment obviates this case entirely in a way I'm not seeing."
Many times the answer is that with a different structure you don't need deep mutation to be a critical part of your program. After all, "deep mutation" isn't considered a great idea in object-oriented languages either, where it constitutes a violation of the Law of Demeter [1], either in letter or in spirit (i.e., creating a chain of methods to set some deep value may in letter follow the Law but can still be a violation in principle).
But if you do need it, Haskell does have a rather nifty mechanism for mutation patterns to be first-class elements themselves through "lenses", which capture as a first-class value some access pattern and mutation pattern on a given value. And while one of its original purposes is to allow Haskell like
value %~ property1 ^. property2 =. newValue
such that in a monadic context that will pretty much do what you'd expect as an imperative programmer, it also means (property1 ^. property2) is itself a value that can be used and passed around like any other, and allows for things like creating generic functions that take "a thing, and a thing that will extract a Name from that thing, and will return a new copy of the original thing with the name all uppercase" or something like that. And there's a whole bunch of other ways to make that stuff sing and dance too, if you're in the mood.
You can even do really melty stuff like have a lens that will expand an int into its bits, allow you to manipulate those bits as if you had an array of bool, and then will re-pack them into the int for you. Lenses can take any arbitrary slice out of an object as long as you can express the extraction and the creation of a new object putting the stuff back, and then they can be composed together as-needed. It can be powerful, but it can get pretty brain-melty too.
Let me add that lenses are not just a Haskell thing. It's a simple (and beautiful IMHO) concept from functional programming that can be introduced in many languages. Also, you can have lenses without the cryptic operators.
The cryptic operators are by far the worst thing about learning the basics of Haskell.
The language is already hard to learn because of all the wonderful and mindblowing concepts but the syntax is super frustrating and takes the difficulty to another level.
Haskell obviates the need to do deep manipulations in an ad hoc 'ugly' way usually via lenses. Personally, i think most imperative languages are sufficiently less sophisticated than a state monad plus lens thay it is substantially more difficult to use them.
> But I also remember Haskell seemingly forcing you to push all your imperative code up to the surface layer of your otherwise pure program
I have no idea what you're talking about. You seem to be confusing effectful code with imperative code. Haskell's do notation -- which lets you write with an imperative syntax -- can appear anywhere, including pure code. On its own imperative code does not necessarily mean effectful code and mutation does not require us to give up on purity. Moreover, if you do want machine level mutation for performance reasons or because an algorithm is more easily expressed in that way, you can always drop into the (again pure) ST monad.
Basically, I think you are criticizing haskell from a place of ignorance.
Frameworks like immutable.js are not comparable to haskell. These are immutable data structure libraries built for languages where immutable data is an afterthought at best, if its even considered at all. Obviously these are going to be clunkier to use. Haskell is not that though.
But I've done a bit of Clojure, and I've done a bit of Immutable.js, and particularly when you have a deeply-structured piece of data, "mutating" something a few levels down gets really ugly.
But I've done a bit of Clojure, and I've done a bit of Immutable.js, and particularly when you have a deeply-structured piece of data, "mutating" something a few levels down gets really ugly. Now, maybe something about Haskell Enlightenment obviates this case entirely in a way I'm not seeing. But I also remember Haskell seemingly forcing you to push all your imperative code up to the surface layer of your otherwise pure program, which sounds great unless you need to do really meaningful things that are by nature imperative.