If you need to temporarily bypass purity there's unsafePerformIO and friends (or they can occasionally be used permanently if you do a lot of analysis I believe).
It's a pretty practical language compared to its reputation.
They are lamenting that implementing a trivial sorting function in Haskell is difficult.
quicksort [] = []
quicksort (x:xs) =
let smallerSorted = quicksort [a | a <- xs, a <= x]
biggerSorted = quicksort [a | a <- xs, a > x]
in smallerSorted ++ [x] ++ biggerSortededit: also, how is the pivot element selected there?
Obviously picking first element as pivot makes for a very poor Quicksort. Though I presume a better approach, say median of first, mid and last elements, would add but an extra line?
It is only linear when you force it to evaluate, which ideally happens only once (amortized) in your program when the result is consumed. As long as you are careful, you don't get the bad quadratic performance that you'd get from something like eagerly appending a character to a string in a loop in Java. There are either gotchas, though, including relying on the compiler to do things in constant space that naively look linear, and also knowing when the compiler won't help you and you have to structure your computation manually.
My biggest problem with Haskell is how GC and lazy-evaluation makes it very difficult to reason about what the hardware is actually doing at a given point. I know there ways to inspect and control it, but I've found myself preferring languages that have simpler mental models.
Quicksort isn't really hard, you just do it in IO and it looks about like what you'd see in any imperative language. (or you can use ST, but then you have to know about ST).
For non-debug, you may be overestimating how useful it is to output from pure functions in production.
In general when you are in a situation where you need to change the Monad you're under, there's techniques and libraries for that (one way you define it once, give it a name, etc.).
The biggest pain tends to be going from pure to monadic in the first place, which last I used Haskell there wasn't much for but to just do it. There may be more tooling now, haven't seen.
As soon as you need to control the order of execution, memory usage, IO, or any combination of those, you are fighting the language and into territory of things you can technically do and out of the realm of things the language makes easy.