Because Algebraic Effects are unfamiliar, we explain a part of it with something that's familiar to most developers.
This is just like the story of the blind men with the elephant. The elephant (algebraic effects) is a whole other different beast, but to help the beginner understand the first parts of it, we say the truck is like a snake (resumable exceptions) or that the ear is like a fan (dependency injection).
Algebraic effects are a new type of control flow. When an effect is raised, the handler (defined further up the call stack) has several options:
- handle the effect and resume the computation where the effect was raised.
- handle the effect, but resume the computation multiple times back to where the effect was raised.
- decide not to handle the effect, and just exit, at which point the execution resumes right when the handler was called further up the stack, and everything executed since then is as if it never happened. This is a way of doing back tracking.
In addition, what's algebraic about algebraic effects is that they usually come in a group, and are designed with a compositional algebra, so you can safely compose them in different ways. A common one is commutativity, so it doesn't matter what order you execute them in. Of course, not all algebraic effects have commutativity, as that has to be designed by the implementer. Monads are different in that they often don't compose without monad transformers, so that can be finicky.
Hence, with all these things together, you can use them to implement control flow in other languages that are typically baked into the language, such as exceptions, coroutines, generators, async/await, probabilistic programming, backtracking search, dependency injection. You might also invent your own!
But most commonly here, we use them to separate the *intent* to do a side-effect from the actual execution of the side-effect. That way, side-effects are controlled, and it becomes easier to reason about our code.