In computer programming, a pure function is a function that has the following properties:[1][2]
Thus a pure function is a computational analogue of a mathematical function. Some authors, particularly from the imperative language community, use the term "pure" for all functions that just have the above property 2[3][4] (discussed below).
The following examples of C++ functions are pure:
floor
, returning the floor of a number;max
, returning the maximum of two values.void f() {
static std::atomic<unsigned int> x = 0;
++x;
}
x
can be only observed inside other invocations of f()
, and as f()
does not communicate the value of x
to its environment, it is indistinguishable from function void f() {}
that does nothing. Note that x
is std::atomic
so that modifications from multiple threads executing f()
concurrently do not result in a data race, which has undefined behavior in C and C++.Pure functions can be implemented in Wolfram Language using #
to refer to inputs and &
to indicate the end of the function. For example, the following code defines the factorial function, such that fact[n]
is equivalent to n!
.[5]
fact = If[#1 == 0, 1, #1 #0[#1 - 1]] &
#1
refers to the first input, while #0
refers to the function itself, making a pure recursive function simple to implement.
The following C++ functions are impure as they lack the above property 1:
int f() {
return x;
}
sin()
is not pure, since its result depends on the IEEE rounding mode which can be changed at runtime.int f(int* x) {
return *x;
}
void f() {
static int x = 0;
++x;
}
f()
is called concurrently, the code exhibits a data race. Pure functions can fail or never return, but they must do this consistently (for the same input). However, f()
may, or may not, fail, depending on whether the upper bound of allowed signed int
value was reached or a data race happens, or not.The following C++ functions are impure as they lack the above property 2:
void f() {
static int x = 0;
++x;
}
void f() {
++x;
}
void f(int* x) {
++*x;
}
void f() {
std::cout << "Hello, world!" << std::endl;
}
The following C++ functions are impure as they lack both the above properties 1 and 2:
int f() {
static int x = 0;
++x;
return x;
}
int f() {
int x = 0;
std::cin >> x;
return x;
}
I/O is inherently impure: input operations undermine referential transparency, and output operations create side effects. Nevertheless, there is a sense in which function can perform input or output and still be pure, if the sequence of operations on the relevant I/O devices is modeled explicitly as both an argument and a result, and I/O operations are taken to fail when the input sequence does not describe the operations actually taken since the program began execution.
The second point ensures that the only sequence usable as an argument must change with each I/O action; the first allows different calls to an I/O-performing function to return different results on account of the sequence arguments having changed.[6][7]
The I/O monad is a programming idiom typically used to perform I/O in pure functional languages.
Functions that have just the above property 2 allow for compiler optimization techniques such as common subexpression elimination and loop optimization similar to arithmetic operators.[3] A C++ example is the length
method, returning the size of a string, which depends on the memory contents where the string points to, therefore lacking the above property 1. Nevertheless, in a single-threaded environment, the following C++ code
std::string s = "Hello, world!";
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int l = 0;
for (int i = 0; i < 10; ++i) {
l += s.length() + a[i];
}
can be optimized such that the value of s.length()
is computed only once, before the loop.
In Fortran, the pure
keyword can be used to declare a function to be just side-effect free (i.e. have just the above property 2).
Since pure functions have the same return value for the same arguments, they are well suited to unit testing.