If you want to just learn it and have no prior JVM knowledge then probably Haskell is the best language to learn the concepts. You then want to first get an idea how to explicity describe and control effects (using the IO-type in Haskell). The next step is then to handle errors (with IO and Either types) and then how to handle effectful streams.
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.
Oh sorry, I guess I misread your post. But yeah, Scala alone doesn't make the difference compared to Elixir, it's ZIO or alternatively cat-effect. Scala as a language just enables those libraries to exist and be used ergonomically. I don't see a reason why something similar couldn't happen for the BEAM, but there hasn't be a language that supports those concepts yet.
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.