Side Effects and Functional Programming
Posted on July 9, 2012.
Today we’ll examine why functional languages have a reputation of being a mere novelty of academia, while imperative languages have dominated the software industry.
In my last post I mentioned that pure functions cannot have side effects. It follows from this definition that pure functions are referentially transparent), which means that evaluating a function multiple times with the same arguments will always yield the same result. How then could one implement, say, a function that returns the current time? Or one that generates a random number? Or one that reads the next line in a file? All of these functions in an imperative language would return different values if they were invoked multiple times. Isn’t the whole point of computer programs to do side effects like these?
This is the problem that the functional paradigm faces. Software engineers write programs that do useful tasks. Functional languages emphasize writing functions that, in essence, do nothing but return a value, which makes these languages very difficult to use if you’re trying to write a chat client, a web browser, or a first-person shooter.
Here is one solution. Let’s define a main function and implement the famous hello world program in it. I’ll use a fictional dialect of Python we’ll call Fython (for “functional Python”):
def main():
print("Hello, World!")
Stop right there! Our print function has side effects. That’s not functional at all. Let’s try this instead:
def main(old_universe):
new_universe = print("Hello, World!", old_universe)
return new_universe
Aha! Our program is functional once again. Instead of directly printing to the screen, we return a universe in which the printing had been done. In a sense, we are now describing a computation instead of executing one, which is the essence of the declarative paradigm. We could imagine a runtime which applies this function, with the current universe as the argument, and then assigns our universe to the universe returned by this program (thereby modifying our universe such that the computation has been performed). So the runtime does the dirty, stateful work for us, while we only need to describe what the universe would look like if a “Hello, World!” message was printed to the screen. What if we want to get some user input, and then print it back to the screen?
def main(old_universe):
input, intermediate_universe = get_input(old_universe)
new_universe = print("You typed: " + input, intermediate_universe)
return new_universe
Our program takes the current universe, then defines an intermediate universe in which a user has entered a string. With that string and intermediate universe, we define a new universe in which that string has been printed back to the user. The runtime then applies these transformations to our universe, and voilà! Our program has been run.
A pattern should now become clear. All of this is really just a guise for function composition, which is how we can enforce an ordering to the evaluation of our expressions. If our functional program is just an unordered set of equations, how does the runtime know to get user input and then print a message, and not the other way around? The expression which “prints” depends on the universe returned by the expression which “gets the input”, just how in the expression f(g(x)), g(x) must be evaluated before f(g(x)).
However, what happens if we try to run something like:
def main(old_universe): input1, new_universe1 = get_input(old_universe) input2, new_universe2 = get_input(old_universe) final_universe = print(input1 + input2, old_universe) return final_universe
If you run this program, will it ask you for input once or twice? Is this even a valid program? Stay tuned to find out!