The Pure function theme is something that has captured the attention of many people over time. Whether due to its relevance in today's society, its impact on history or its influence on people's daily lives, Pure function has been a topic of constant debate. In this article, we will explore different aspects related to Pure function, from its origins to its evolution today. We will analyze its importance in different contexts and its impact on people's lives, offering a complete and detailed vision that allows us to understand its relevance in today's society.
In computer programming, a pure function is a function that has the following properties:
Some authors, particularly from the imperative language community, use the term "pure" for all functions that just have the above property 2 (discussed in § Compiler optimizations 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++.The following C++ functions are impure as they lack the above property 1:
int f() {
static int x = 0;
++x;
return x;
}
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;
}
int f() {
int x = 0;
std::cin >> x;
return x;
}
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 a 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.[clarification needed]
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.
The I/O monad is a programming idiom typically used to perform I/O in pure functional languages.
static int fact(int n) {
return n<=1 ? 1 : fact(n-1)*n;
}
int fact_wrapper(int n) {
static int cache;
assert(0<=n && n<13);
if (cache == 0)
cache = fact(n);
return cache;
}
|
C program for cached computation of factorial |
The outputs of a pure function can be precomputed and cached in a look-up table. In a technique called memoization, any result that is returned from a given function is cached, and the next time the function is called with the same input parameters, the cached result is returned instead of computing the function again.
Memoization can be performed by wrapping the function in another function (wrapper function).
By means of memoization, the computational effort involved in the computations of the function itself can be reduced, at the cost of the overhead for managing the cache and an increase of memory requirements.
Functions that have just the above property 2 – that is, have no side effects – allow for compiler optimization techniques such as common subexpression elimination and loop optimization similar to arithmetic operators. 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 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int l = 0;
for (int i = 0; i < 10; ++i) {
l += s.length() + a;
}
can be optimized such that the value of s.length()
is computed only once, before the loop.
Some programming languages allow for declaring a pure property to a function:
pure
keyword can be used to declare a function to be just side-effect free (i.e. have just the above property 2). The compiler may be able to deduce property 1 on top of the declaration. See also: Fortran 95 language features § Pure procedures.pure
attribute specifies property 2, while the const
attribute specifies a truly pure function with both properties.constexpr
of C++ (both properties). See also: C++11 § constexpr – Generalized constant expressions.Since pure functions have identical return values for identical arguments, they are well suited to unit testing.
Here are the fundamental properties of a pure function: 1. A function returns exactly the same result every time it's called with the same set of arguments. In other words a function has no state, nor can it access any external state. Every time you call it, it behaves like a newborn baby with blank memory and no knowledge of the external world. 2. A function has no side effects. Calling a function once is the same as calling it twice and discarding the result of the first call.
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect.
assert()
aborts with an error message if its argument is false; on a 32-bit machine, values beyond fact(12)
cannot be represented, anyway.