I figured this out using the following analogy, which I will express using JavaScript.
How can side expressions be expressed?
1. Function
This is obviously the first thing that comes to mind:
var launchRockets = function () { prepareRockets( queryDBForPreparationParameters() ) launchAllPreparedRockets() outputResults() }
You can see an effective function that calls a bunch of other spectacular functions that themselves can create unknown effects with all the ensuing consequences.
2. Instructions
Another way to express this would be to compile a set of instructions describing these efficient calculations for some function for subsequent execution. (Ever made a SQL query?)
var launchRocketsInstructions = [ { description: "Prepare rockets", parameters: { description: "Query a DB for preparation parameters" } }, { description: "Launch all prepared rockets" }, { description: "Output results" } ]
So what do we see in our second example? We see an immutable data tree describing the calculation, not its execution immediately. There are no side effects here, and we can use pure functions to compile this data tree. And what are essentially side effects in Haskell. The entire infrastructure provided by the language: monads, IO , do -notation are just tools and abstractions that simplify your task of compiling a single instruction tree.
Of course, in order to really follow these instructions, you will eventually have to go into the wild world of side effects. In the case of JavaScript, it will be something like execute(launchRocketsInstructions) , in the case of Haskell, it is the runtime that executes the root of the command tree, which you create using the main function of the main module, which becomes the only entry point of your program. Thus, the side effects in Haskell actually occur outside the scope of the language, so it is clean.
source share