Yes! I think side-effects are just one little use-case for monads. They can be useful any time you want to compose functions together but you want to do something else with the composition and data. As Bartosz Milewski so wonderfully explained here [1], Monads are just "a way of composing special types of functions" or "function composition with some kind of flair!"
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]
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]
[1]: https://youtu.be/i9CU4CuHADQ?t=2363