Your second point I disagree with completely. Duff's device is non-idiomatic C that relies on oddities of C syntax (fall-through of cases in a switch statement, and the ability to jump into the middle of a loop). My code is reasonably idiomatic Haskell.
I should also stress that I don't think this being somewhat difficult to read is really bad. Just saying that I would expect a lot of folks to balk at it.
In particular, it naturally splits apart the different function calls. So you can look at the parts separately: L.intercalate "\n", cut (b'-b+1), map toChar, elems arr. Now you have a nice little pipeline.
Perhaps to somebody without Haskell experience, the version with parentheses would be easier to understand:
L.intercalate "\n" (cut (b'-b+1) (map toChar (elems arr)))
However, in general, I find the $ version easier to follow.I also think that this version is better than the same code broken up into arbitrary temporary variables. The pipeline is short and clear; adding temporary bindings would just give me more things to keep in my mind at the same time.
One way to think about this is that the $ is very similar to the | in bash, just backwards. So in a bash-like pseudocode, this is something like:
elems arr | map toChar | cut (b' - b+1) | L.intercalate "\n"
Hopefully that clarifies the structure of the code and why that structure is actually very reasonable.Basically, my point is that the given code is clear and simple; chances are you find it hard to read because Haskell is somewhat foreign and not because the given code is particularly complicated or obfuscated. In fact, I think this way of writing the code is clearer than what you would get in most other languages because it forms a very clear sequence of transformations on the input.
1. Get the elements of the array as a list (elems)
2. Convert each element to a char (map toChar ... toChar is defined elsewhere - it's also a one-liner).
3. Cut the list into sublists of the correct length (cut (b'-b+1)).
4. Join the lists together into a single string, separating them with newlines (L.intercalate "\n").
Three of the four functions are curried. I contend that without currying by default, it would take more resources (in terms of lines, characters and mental effort) to write this function.
[0] https://github.com/chris-taylor/Life