Pure and dirty functions, effects, signal processing, side effects: pure functions

January 12, 2021

The first part of my terminology series can be found here

Note that I’ve broadened the topic I’m covering here. Apart from dirty functions announced yesterday, I also mentioned signals. The title is long-ish, but let’s keep it this way for now :) Today, we’ll be talking about pure functions.

Pure functions are functions with no effects and no side effects. :) Or functions as they are understood in math. Their output is determined by the parameters only, and they have just one output, that is, the value they return.

Here are some signs the function isn’t pure:

  1. It has no result (the functions returns void or Unit). In theory, this could be a pure function. There would be little point to this, though. It could only ever be used as some mock output function.

    A typical example is printf/println.

  2. It has no arguments. Again, in theory, you could make such a function pure. There would be little point to this, though, as it could only be used as some input helper function.

    A typical example is currentTime.

  3. Being called with the same parameters at a different time, it can return different results. Any such function wouldn’t be pure by its very definition.

    A typical example Files.readString(Path.of(``/tmp/test’’))

  4. Being determined, it has an observable effect apart from returning the value. Again, any such function isn’t pure by definition.

    Typical examples include: determined caching and logging in determined functions.

What’s so special about pure functions we had to come up with a separate name? Pure functions are referential transparent. It means that you can replace the function invocation with the result it will generate, and this won’t have any impact on the program’s behavior.

This has some implications that when considered, transform pure functions into the perfect building blocks.

Reading modularity

While reading through code, you can ignore all the details that go into the implementation of pure helper functions, thus saving the "stack" and the "heap" of your brain. This is achieved thanks to the fact that the effects of invoking a pure function are obvious. There’s no need to peruse its code to see whether there are any unidentified "unidentifieds", e.g., a global state that changes the function’s behavior, or a global state changed by the function per se and changing the behavior of the code you’re trying to understand. If you need to make any changes to some other place in the code and you know what the result of the pure function call will be, it doesn’t matter how this result is achieved. That’s because by definition, the place you’ll be changing isn’t in any way connected to the pure functions implementation.

Bullet-proof refactoring

Pure function calls can be freely rearranged, moved back and forth between methods, inlined, cached, etc. Again, per definition, no one will notice. Well, apart from the compiler, as it’ll just refuse to compile this.

Testing

Pure functions testing is a trivial task. Establish the parameters, call the function, and check the output. There’s no need to perform slow I/O operations, run any external service, use mocks, bother yourself with testing in multi-thread environment, emulate input/output errors, or endure any other suffering that would be inevitable with impure function testing.

Composition/reusability

Pure functions can be easily used as building blocks for more complex functions, without having to worry about unexpected effects. Conversely, a validation function that instead of just returning the validation result, also saves the validation request into the database could hardly prove useful in some different context.

Parallel execution

Again, by definition, pure functions can be run simultaneously and/or transferred for execution to other threads.

Caching

With pure functions, cache invalidation is not a problem anymore.

Optimization

I’m too lazy to look around to prove this is actually used in the wild. In theory, though, thanks to refactoring security, compilers can be much more agressive in optimizing pure functions.

Free contract

Pure functions usage automatically gets you a contract.


If pure functions are all so great, why isn’t everyone writing nothing but pure functions? Because we’re creating programs for the sake of their effects on our real physical world. To be continued (rus) :)