digitalmars.com                        
Last update Sat Oct 7 16:49:27 2023

Purity

written by Walter Bright

Sep 21, 2008

Pure functions have some interesting and useful properties. A pure function is one that only depends on its parameters, and its only output is its return value. In the D programming language, the requirements specifically are:

An example of a pure function in D is:

pure int foo(int x, const int[] a)
{
   int sum = 0;
   foreach (s; a)
       sum += s;    // mutable non-static local variables are allowed
   return x * sum;
}

An impure function:

int bar(int x, int[] a)
{  static int sum = 0;
   foreach (s; a)   // reference to mutable array contents
       sum += s;    // read/write to mutable persistent variable
   return x * sum;  // read of mutable persistent variable
}

These properties can be checked at compile time if the function is typed as being pure. A common question is that if the compiler can check to see if a function is pure, why annotate the function? Why not just let the compiler check for purity, and if so, then accrue the advantages of purity? The answer is that purity is a property the programmer needs to know about. If purity was inferred rather than specified, a seemingly innocuous change in one function can make it impure, and every function that calls it impure, etc. There will be no reasonable way for the programmer who expects a function to be pure, to see if it actually is pure. If he does check (which might be arbitrarilly difficult to do) and it is not pure, he has no reasonable way to determine why the compiler thought it was impure.

So now that we have pure functions, what are they good for?

The first is self-documentation. A person trying to understand a code base, once they see that a function is pure, they know it only depends on its arguments, has no side effects, and there’s no monkey business going on inside it. This greatly reduces the cognitive load of dealing with it. A big chunk of functions can be marked as pure, and just this benefit alone is enough to justify supporting it.

Pure functions do not require synchronization for use by multiple threads, because their data is all either thread local or immutable.

Common subexpression elimination is an important compiler optimization, and with pure functions this can be extended to cover them, so:

int x = foo(3) + bar[foo(3)];

need only execute foo(3) once.

User level code can take this further by noting that the result of a pure function depends only on the bits passed to it on the stack (as the transitivity of invariants guarantees the constancy of anything they may refer to). Those bits can therefore be used as a key to access memoized results of the function call, rather than calling the function again.

Pure functions can be executed asynchronously. This means that not only can the function be executed lazily, it can also be farmed out to another core (this will become increasingly important as more cores become commonplace).

They can be hot swapped (meaning replaced at runtime), because they do not rely on any global initialization or termination state.

Functional programming capability is an exciting addition to imperative programming languages.

Further reading:

Grafting Functional Support on Top of an Imperative Language

is Andrei Alexandrescu’s explanation of how functional purity works in the D programming language.

Verifiable Functional Purity in Java

shows how pure functions could be added to Java.

Home | Runtime Library | IDDE Reference | STL | Search | Download | Forums