Some monads are also handy for structuring certain kind of code---for example, the non-determinism monad can be handy for writing searches, etc.
I was recently writing a Unger-parser-like natural language parser and I wanted to compose functions that took the parse results from one parse function and then parse something else. I noticed that every time I wanted to pass the results to another function I had to do something with the leftover tokens and also coallate the error messages that might have been building up. What if I used monads to automatically handle the resulting tokens and error messages each time I reached for another parse function? I wrote a monadic binding function that handled the tokens and error messages for each parse function, and then I was able to happily compose my parsing functions in a much more clean and concise way.
Monads are great anytime you want to do something more along with your function composition other than just passing one piece of data directly from one function to another.
"And then you say... What if I used a different kind of composition? Also function composition, but with some kind of flair, something additional. If I redefine composition, I have this one additional degree of freedom in defining composition. If I use this additional degree of freedom, I am using a monad." - Bartosz Milewski [1]
Various monads basically help you manage things like configuration parameters that affect your computations. Your code can all be deterministic, and yet using a monad can help you write cleaner code.
Just because the IO monad is used for impure actions doesn't mean that monads in general are used for impure actions.
[0] There are a few builtins that have side-effects: `input` and `inputs` (which consume inputs), and then there's `stderr` and `debug` (which emit output to `stderr`). Other than this, every jq program represents a pure transformation of its input.