In imperative programming, functions are allowed to have side-effects, such as modifying the value of variables, writing to files, accessing the network, etc. The second time the same function is run, it can detect the previous side effects and return something different. A simple C example is:
int i = 0;
int foo() {
return i++;
}
Calling this multiple times will generate different numbers. As another example:
int foo(int *p) {
return (*p)++;
}
Even if you call the above with the same parameter, i.e. the same pointer, the result will be different because of the increment.
In pure functional programming, side effects like i++
are forbidden by design. In this way, the output of the function must depend only on the value of its arguments. E.g. if in Haskell we have a function f
with type Int -> Int
, we can be sure that f 3
will always return the same integer at every call.
Well, the above is not entirely true -- a program eventually has to do some I/O, or would be meaningless. Because of this, side-effects must be available in some form. In Haskell, functions with side effects are actually allowed, but their type must tell these are not pure functions. E.g. if function g
has type Int -> IO Int
, then it can perform I/O and have side-effects: running print =<< g 3
can print different values every time, just as in imperative programming.
So, summing up, in pure functional programming functions having side effects have a different type from functions who do not. Often, in a well-designed functional program, I/O and side-effects are rarely needed, and pure functions comprise the vast majority of the code. Because of this, sometimes we say that "pure functional programming forbids side effects", since most of the code is written under this constraint.