8 ms·
That's part of it. For IO/State, the idea is basically, if you wite a function that does something, you have to test it by doing that thing, so, instead, let's
by T-R 11y ago
That's part of it. For IO/State, the idea is basically, if you wite a function that does something, you have to test it by doing that thing, so, instead, let's write recipes to do things, and then run them (recipes here just being sets of instructions - not trying to make a weird kitchen metaphor, just seemed more clear than "build computations"). Then, the code to build the recipes can be pure, and we can maybe do neat things like check recipes for equality, change up how we run them to do practice runs while taking notes, pass the recipes around and modify them, and build up libraries of not just recipes, but recipe modifications. A nice side effect is that it lends itself really well to separation of concerns/testable code - if your language is type-checked, it enforces it. If not, it's kind of optional, but there's not much reason not to keep it pure, since you're effectively doing dependency injection already. It's useful for non-side-effecty things, too - for State monads without side-effects, our "recipe" is a state machine, and it means we're being explicit about our inputs, which also makes it easy to do stuff like lookahead - changing the state and then dropping back to a previous one.
It turns out this style of recipe-building, when you get down to the minimal stuff you need for it, is really useful for any sort of computation-in-a-context (for a recipe, the result of running the recipe is the value "in" the context - you can "modify" it by adding to the recipe, same as you can "modify" a function's return value by composing it with another function). Basically, the interface says that "return" lets you put something in a context, "join" describes how you can compose contexts, and kleisli-compose says that if you have two functions that return results in a context ( a -> ctx(b) and b -> ctx(c) ), you can compose those functions by composing the resulting contexts with "join". The end result is that if you have some context, like a recipe, you can apply changes to it ("bind"). In fact, if you have no context (the Identity monad) kleisli-compose reduces to just normal function composition, and bind reduces to application.
TL;DR: separation of concerns is a really nice side-effect of what's just a really broadly applicable interface for "more complicated but still composable stuff".