The combination of those 3 allows you to do things like "run those two processes at the same time, where one process does something every minute and the other when a request comes in. Then merge the two together, only execute one action per 30 seconds even if there are more events from the two combined processes. But if an action fails, retry it 3 times while waiting a few seconds between each try. If the 3rd try fails then, depending on the error, stall the process that runs every minute for 10 minutes and only accept events from the request-based process..."
And so on. I found that in languages that don't support those explicit (pure functional) concepts, it is almost impossibly hard to get the logic right when multiple levels of concurrency and error-handling come in.
If you have prior JVM knowledge or want to build something productive then I suggest to start with Scala and ZIO (https://zio.dev/) using the same concepts as described for Haskell.
Then afterwards, have a look into STM (https://zio.dev/reference/stm/ for Scala or https://hackage.haskell.org/package/stm for Haskell). Those essentially allow you to do things that database often do for you (fine grained locking) - just more powerful and composible and in-memory without database IO.
With those things you are well equipped to deal with problems of type #2.