| I like to explain functional programs like this. Functional programs don't _do_ anything, they are a list of commands some external interpreter (what you call the runtime, but doesn't have to be a runtime) will execute or compile. Functional programs only contain descriptions of the various commands, but it is an external interpreter executing the commands. Functional programs return "recipes". "recipes" are picked up by some executor that will turn them in dishes (the side effect). If all of this seems generic allow me some typescript. ``` // we can declare a a function that takes no argument and return a value IO. type IO<A> = () => A declare const program: () => void; // thus can be rewritten as declare const program: IO<void> // this is the PURE function I can reason about declare const execute: IO<void> => void // this is the interpreter that will execute the commands and have side effects ``` Now, let's declare a side effectful functions: ``` const log: (s: string) => IO<void> // desugarized: (s: string) => () => void
``
` Notice how the signature is similar to the standard console.log:
(s: string) => void except that it's lazy. Laziness is one of the most convenient ways to express "commands" rather than side effects in a language like JavaScript, but there are also alternatives. Now we can have a pure program that logs to the console some string. `log("foo")` does not "execute" any console.log, it still needs to be executed: ``` const program: IO<void> = log("foo"); // program() will actually print "foo" in stdout ``` Note: we could've encoded IO in different ways, e.g. with a struct/interface rather than a function and had the interpreter actually reason about the side effect on its own. Now, the only missing part is: how do I compose such IO functions together? Well, there are various ways but the most common ones are applicative functors and monads. I am not going to delve deeper in this comment on those topics because it would take long but I hope I have transmitted my point: in functional programs you return programs (I like to think about them as recipes, recipes don't DO anything), those programs may compose effectful commands, the actual execution of the commands is shoved inside an external interpreter. This is quite obvious in some languages like Haskell, it's less obvious in others. |
But why would someone want to do this? It feels like mental overhead. So what advantages are there to a system like this?