pure function

{{Short description|Program function without side effects}}

{{how-to|date=May 2024}}

In computer programming, a pure function is a function that has the following properties:{{cite web |url=https://www.schoolofhaskell.com/school/starting-with-haskell/basics-of-haskell/3-pure-functions-laziness-io |title=Basics of Haskell |author=Bartosz Milewski |date=2013 |website=School of Haskell |publisher=FP Complete |access-date=2018-07-13 |archive-url=https://web.archive.org/web/20161027145455/https://www.schoolofhaskell.com/school/starting-with-haskell/basics-of-haskell/3-pure-functions-laziness-io |archive-date=2016-10-27 |url-status=dead }}{{cite web |url=https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch03.md |title=Professor Frisby's Mostly Adequate Guide to Functional Programming |author=Brian Lonsdorf |date=2015 |website=GitHub |publisher= |access-date=2020-03-20 }}

  1. the function return values are identical for identical arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams, i.e., referential transparency), and
  2. the function has no side effects (no mutation of non-local variables, mutable reference arguments or input/output streams).

Examples

= Pure functions =

The following examples of C++ functions are pure:

{{ulist

|1=floor, returning the floor of a number;

|2=max, returning the maximum of two values.

|3=the function f, defined as

void f() {

static std::atomic x = 0;

++x;

}

The value of 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++.

}}

= Impure functions =

The following C++ functions are impure as they lack the above property 1:

{{ulist

|1=because of return value variation with a static variable

int f() {

static int x = 0;

++x;

return x;

}

|2=because of return value variation with a non-local variable

int f() {

return x;

}

For the same reason, e.g. the C++ library function sin() is not pure, since its result depends on the IEEE rounding mode which can be changed at runtime.

|3=because of return value variation with a mutable reference argument

int f(int* x) {

return *x;

}

|4=because of return value variation with an input stream

int f() {

int x = 0;

std::cin >> x;

return x;

}

}}

The following C++ functions are impure as they lack the above property 2:

{{ulist

|1=because of mutation of a local static variable

void f() {

static int x = 0;

++x;

}

|2=because of mutation of a non-local variable

void f() {

++x;

}

|3=because of mutation of a mutable reference argument

void f(int* x) {

++*x;

}

|4=because of mutation of an output stream

void f() {

std::cout << "Hello, world!" << std::endl;

}

}}

The following C++ functions are impure as they lack both the above properties 1 and 2:

{{ulist

|1=because of return value variation with a local static variable and mutation of a local static variable

int f() {

static int x = 0;

++x;

return x;

}

|2=because of return value variation with an input stream and mutation of an input stream

int f() {

int x = 0;

std::cin >> x;

return x;

}

}}

I/O in pure functions

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.{{clarify|date=November 2022}}

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.{{cite book|last1=Peyton Jones|first1=Simon L.|title=Haskell 98 Language and Libraries: The Revised Report|page=95|date=2003|publisher=Cambridge University Press|location=Cambridge, United Kingdom|isbn=0-521 826144|url=http://it.bmc.uu.se/andlov/dev/books/haskell98-report.pdf|accessdate=17 July 2014|ref=haskell98}}{{cite web|last1=Hanus|first1=Michael|title=Curry: An Integrated Functional Logic Language|page=33|url=http://www.informatik.uni-kiel.de/~curry/papers/report.pdf|website=www-ps.informatik.uni-kiel.de|publisher=Institut für Informatik, Christian-Albrechts-Universität zu Kiel|accessdate=17 July 2014|ref=curry14|archive-url=https://web.archive.org/web/20140725231927/http://www.informatik.uni-kiel.de/~curry/papers/report.pdf|archive-date=25 July 2014|url-status=dead}}

The I/O monad is a programming idiom typically used to perform I/O in pure functional languages.

Memoization

{{Main|Memoization}}

The outputs of a pure function can be cached in a look-up table. 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).{{cite book | last=Aley | first=R. | title=Pro Functional PHP Programming: Application Development Strategies for Performance Optimization, Concurrency, Testability, and Code Brevity | publisher=Apress | series=SpringerLink : Bücher | year=2017 | isbn=978-1-4842-2958-3 | url=https://books.google.com/books?id=_JY3DwAAQBAJ&pg=PA109 | access-date=2024-02-04 | page=109}}

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.

A C program for cached computation of factorial (assert() aborts with an error message if its argument is false; on a 32-bit machine, values beyond fact(12) cannot be represented.{{citation needed|date=May 2024}})

static int fact(int n) {

return n <= 1 ? 1 : fact(n - 1) * n;

}

int fact_wrapper(int n) {

static int cache[13];

assert(0 <= n && n < 13);

if (cache[n] == 0)

cache[n] = fact(n);

return cache[n];

}

Compiler optimizations

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.{{Cite web |title=Common Function Attributes - Using the GNU Compiler Collection (GCC) |url=https://gcc.gnu.org/onlinedocs/gcc-6.1.0/gcc/Common-Function-Attributes.html#index-functions-that-have-no-side-effects-3246 |access-date=2018-06-28 |website=gcc.gnu.org, the GNU Compiler Collection |publisher=Free Software Foundation, Inc.}} 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.

Some programming languages allow for declaring a pure property to a function:

  • In Fortran and D, the pure keyword can be used to declare a function to be just side-effect free (i.e. have just the above property 2).[https://archive.today/20130103042647/http://publib.boulder.ibm.com/infocenter/comphelp/v111v131/index.jsp?topic=/com.ibm.xlf131.aix.doc/language_ref/pure.html Pure attribute in Fortran] The compiler may be able to deduce property 1 on top of the declaration.[https://web.archive.org/web/20190202194918/https://digitalmars.com/d/2.0/function.html#pure-functions Pure attribute in D language] See also: {{slink|Fortran 95 language features|Pure procedures}}.
  • In the GCC, the pure attribute specifies property 2, while the const attribute specifies a truly pure function with both properties.{{cite web |title=Common Function Attributes |url=https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html |website=Using the GNU Compiler Collection (GCC |access-date=22 July 2021}}
  • Languages offering compile-time function execution may require functions to be pure, sometimes with the addition of some other constraints. Examples include constexpr of C++ (both properties).[https://web.archive.org/web/20190325075719/https://en.cppreference.com/w/cpp/language/constexpr constexpr attribute in C++] See also: {{slink|C++11|constexpr – Generalized constant expressions}}.

Unit testing

Since pure functions have identical return values for identical arguments, they are well suited to unit testing.

See also

  • Compile-time function execution{{snd}}The evaluation of pure functions at compile time
  • Deterministic algorithm{{snd}}Algorithm that, given a particular input, will always produce the same output
  • Idempotence{{snd}}Property of operations whereby they can be applied multiple times without changing the result
  • {{annotated link|Lambda calculus}}
  • {{annotated link|Purely functional data structure}}
  • Reentrancy (computing){{snd}}Executing a function concurrently without interfering with other invocations

References