Pure functions in Erlang

disclaimer: relatively new to FP.

As far as I know there isn’t any way to mark functions as pure in Erlang.
Is there any EEP about this ATM? (If not, why not?)

Could we benefit from it by allowing user-defined functions in guards?
Also, could there be some performance improvement due to fact that pure code is easier to run in parallel?

4 Likes

Erlang functions are PURE functions.

As you mentioned about performance, this choice greatly simplifies garbage collection of concurrent processes.

Exceptions to this could be NIFs, as Erlang has no control over that.

If you want “mutable” state, the best way to do that in Erlang is to wrap that in a process (gen_server) and send messages to it. The internal state of that actor can change, but it is all still technically pure functions.

Some FP languages aren’t this strict, like Clojure for example.

2 Likes

I hate to be pedantic, but this is not correct.

While functions in Erlang can be pure, they are not all pure. Functions can have side effects which is direct opposition to the accepted definition of “pure”.

10 Likes

Guards gave other issues than only pureness, except for a few notable exceptions (like length) they have O(1) runtime. Also they need to be builtin without module because it is quite messy when guards would be hot code loaded.

I don’t get the „parallel“ argument: all Erlang functions can be run concurrently and in parallel due to share nothing processes.

5 Likes

Could we than restrict guards to local pure functions or something like that?

Absolutely correct, my nobrainer. Now lets put guards aside for a moment. Roughly speaking, aren’t pure functions in general faster to compute due to different kind of optimizations that can be performed? If they are, can BEAM alone figure out which functions are pure and then threat them as prone to those optimizations?

3 Likes

Thats a pretty generic claim. Why would pure Erlang functions be faster to compute?

3 Likes

Case in point : sending a message to a process :slight_smile:

4 Likes

If a compiler can track purity then it can perform more aggressive optimisations. Erlang doesn’t do this as far as I’m aware, but other languages do.

4 Likes

For example caching return values of functions frequently called with the same parameter or LRU caching.

Thank you, that is what I meant to say.

3 Likes

Memorization is only worth it in rare cases which often depend very much on arguments. Impossible to automate unless one completely ignores the memory usage. Only worth it in rare corner cases which are very application dependent .

Also a sufficiently smart compiler (much less smart than necessary to decide above mentioned optimization) can figure out pureness itself for local only calls, qualified calls can never be pure because of hot code upgrades.

4 Likes

An example where knowing pureness would help:

recursive factorial function called with constant argument like replacing fac(5) with 120

But for that one doesn’t need developer declared pureness, thats quite easy to figure out from the code.

4 Likes

As @peerst pointed out, it would be easy for the compiler itself to figure out which functions are pure, if the compiler would have any need for that information.

However, the compiler has a list of pure BIFs. A BIF is considered pure if it does not depend on the state (of the runtime system) and does not affect the state. For example, self/0 is not considered pure because its return value depends on which Erlang process is running.

When a pure BIF is called with literal arguments, the compiler will evaluate the BIF call at compile time. For example, the call list_to_atom("abc") will be replaced with the atom abc at compile time.

Theoretically, it would be possible to do the same optimization for pure Erlang functions. We don’t do that, mainly because we don’t want to make call tracing and stack backtraces confusing by removing calls to Erlang functions.

9 Likes