The discipline of functional programming is defined by the statement: “No function call can have any side-effects”. Since IO is nothing but a side-effect–it changes the state of the world, from the position of the next read in a file to the state of an output device–it follows from this that no “pure functional” language can do IO. Haskellers distinguish “the runtime” from “the language” in an attempt to contradict this claim, but this hides the very clever way Haskell implements IO via monads to retain the power of a pure functional language in one that is not.
The return value of a pure function is completely determined by its arguments and–notwithstanding monads–return values are identical when they behave identically under all possible circumstances. This is why IO cannot be purely functional: no one knows what a user might type at the keyboard, so IO operations can return anything at all. LIkewise, putting data onto the console is a side effect that cannot be avoided in a programming language that lays any claim to utility.
Side effects go far beyond IO, though. Assignment of a value is a side effect:
x=6 # here we set x equal to six
Subsequent to the assignment operation, x has a value that is different from what it had previously in the flow of control, which is typically handled somewhat differently using functional disciplines.
Unlike imperative programming, functional programming leans heavily on nesting of function calls and not so much on the lexical ordering of statements to determine execution order. While this is a tendency rather than a rule, it is still an important aspect of the functional discipline: when writing code that depends on execution ordering while practicing a functional discipline, it is always worth asking, “Could this be expressed more functionally as nested function calls?”
This can result in the final code being pretty obscure:
x = 6 y = x + 5 z = y*42
mul(42, add(5, 6))
If you remember your RPN calculator with fondness, functional programming might be for you, but not all software developers fall into that category.
Python is a multi-paradigm language, supporting procedural, object oriented, and functional disciplines fairly well. If you’re not a functional purist but appreciate the value that functional disciplines bring to a program, Python is here to help you out.
Two very useful tools in the functional programmer’s toolkit are
reduce(). map() returns an iterable that is the result of an operation applied to an input iterable, and
reduce() can be used to apply an operation that reduces this map to a single scalar value.
Python has a built-in
map() function, and the functools module supplies a
reduce() function to complement it. They are particularly powerful when used with anonymous functions defined via the lambda keyword. For example:
map(lambda x: a*x**2+b*x+c, myList)
This will create a list that consists of the polynomial values computed from the contents of myList.
The functools module also includes partial application as a service, so supposing I have a function:
galambosian(arg1, arg2, arg3)
and I want a different function that always has arg3 set to five:
primary = functools.partial(galambosian, arg3=5)
So Python allows us to take a function and return another function based on it.
Functions are objects in Python, and decorator syntax is another way of transforming one function into a function that does something else:
@someDecorator def decorated(arg1, arg2): …
This creates a function called “decorated” that is called by the function defined by the decorator in the midst of the other work that function does. This is one way to implement separation of concerns using functional techniques: the decorated function is concerned with one job, the decorator with another. Decorator syntax allows the two jobs to be combined without coupling them together in all possible futures.
Functions in Python are not just objects but closures which allows a list of useful functional tricks too long to go into here.
Being a multi-paradigm language has advantages. Python’s OO syntax can make functional syntax somewhat cleaner as well. To take the add/multiply example above, methods returning objects that can have methods called on them can be much clearer to read for people who are not hardcore functional programmers:
class number: def __init__(value): self.value = value def plus(num): self.value += num.value return self def times(number): self.value *= num.value return self
This allows us to write, instead of
mul(42, add(5, 6)):
which is admittedly verbose, but which is also a highly contrived example. The important point is that having methods
return self allows a functional style that is highly readable.
Imperative and procedural programming will always be with us, but an awareness of functional disciplines and how they are supported by multi-paradigm languages like Python allows developers to deploy them where appropriate. Avoiding assignment, treating functions as objects that can be manipulated in their own right to create new functions via decorators or partial application, and specific techniques such as map/reduce all have significant advantages, particularly for distributed applications, because they avoid side effects like modifying internal state.