Search code examples
functional-programmingautomated-refactoring

Converting code to pure functional form


In a sense, any imperative code can be converted to pure functional form by making every operation receive and pass on a 'state of the world' parameter.

However, suppose you have some code that is almost in pure functional form except that, buried many layers of function calls deep, are a handful of imperative operations that modify global or at least widely accessed state e.g. calling a random number generator, updating a counter, printing some debug info, and you want to convert it to pure functional form, algorithmically, with the minimum of changes.

Is there a way to do this without essentially turning the entire program inside out?


Solution

  • This isn't technically all that hard.

    If all you want to do is avoid modifying global state, e.g, make the code re-entrant, then for any side-effecting function F that reads X, writes global variable Y, and returns Z, transform it into F' that reads X, modifies a struct containing Y passed to it, and returns Z. If A calls B calls .. transitively calls F, and F modifies global Y, then A' must build a reference to a struct containing Y and pass it to B. Now all your functions only modify values passed to them; they have no side effect on global state. (Well, we can argue about what A' does).

    [Somebody may complain that (File/Screen/Device) Output done by F can't be handled. Well, either you want the immediate side effect on the world or you don't. If you don't, add the "state" of the Output as Y, and modify that; A' can return the desired Output as a result.]

    If you insist on making the the program functional then any side effect of F on global Y has be changed to pass Y in, copying Y while changing it to produce Y', and then F' must return a pair . Callers of F must pass in Y, and use the resulting Y' in all code reachable from the call site.

    The bit about copying gets you into real trouble: where is the logic to do a deep copy of Y to be found? If you find it, you may discover that deep copy produces an enormous structure and your storage demand gets impossible quickly. Now you need to find a way to make Y' share the parts of Y that haven't changed.

    Now, if you want to do either of these tasks on a big code, you probably don't want to do it by hand; people are bad at handling this kind of detail and they want to cheat/rewrite/... If you really want to do this, you want a source-to-source program transformation system, which can mechanically apply the necessary steps.

    I'll note that standard compilation techniques convert programs to so called "static single assignment (SSA)" form, which converts as much of the program, represented in the IR, into a functional program because it is easier to transform/optimize. They still worry about global storage.