When I first got into Erlang (a long time ago) I remember a quote from Joe Armstrong.
Along the lines of:
Apart from the softest of side effects (IO) Erlang is pure
I cannot remember where I read it.
And now in the strictest sense, I understand it’s not true (Eric Meijer talks about this a little too, which helped me understand the implications).
But… a few questions…
Does immutability help mitigate impurities? I.e. your function may have a side effect (message passing / receiving) but at least you are doing it in a cleaner way, any nice ways to think about this?
Does Elixir basically follow Erlang step-by-step or are there any subtle semantics which make it diverge a little (perhaps just more tools available than back in the day, but it feels a little more side effect-y, v much anecdotal from my own pov and perhaps it’s much the same in Erlang now too)
Pureness has the property that you know for sure that if you call the function the first time, the second time or the nth time, the answer will be the same. This has two advantages:
Languages can use this to do memoization, (remembering the answer of the first invocation so a function needs never be called more than once) which makes them fast: efficiency.
Programmers can more easily reason about their code (as there are no ‘hidden’ things when there are no side-effects). This makes it easier to understand why something does (or doesn’t) work: maintainability
I’d say that immutability increases both efficiency and maintainability in its own way. You can have a system with immutability but without pureness, but not the other way around (as changing a property of one of the inputs can be considered a side-effect).
So, to answer 1): Immutability definitely helps to make systems easier to reason about, regardless of purity.
To answer 2): For instance pipelines are constructs that ensure that functions are built very immutably, as you cannot bind to variables in the middle of a pipeline, for instance. So this is something that makes Elixir maybe less side-effect heavy.
I like the impureness of Elixir/Erlang as it makes it a lot easier to write concise code. I have experience in Haskell before, but I would not use it for anything outside of academic projects involving heavy calculations; Doing anything like user-interaction depends too much on a state. Pushing this state to the outside of your application means that whenever this UX-part changes, the application has to be overhauled in a large way. The boilerplate in systems like yesod is very high because of the split between the pure and impure parts of the application.
Of course, message-passing in a pure world is something that can not really happen. The principle of pureness kind of conflicts with the idea of concurrent applications.
One drawback of Erlang’s current pureness-implementation is that only a very small amount of functions (that are ‘known to be pure’) is allowed to be used in guard clauses. This severely limits the amount of pattern-matching that can be done, when compared to pure languages. I would love for this to change; If Erlang would have a proper short-circuiting || and &&, for instance, we could build a whole lot more guard-safe macro functions than are available now.
I’d like to point you towards quite an interesting article written recently by Jose Valim, the language creator – http://blog.plataformatec.com.br/2016/05/beyond-functional-programming-with-elixir-and-erlang/. Basically, if you read the article to the end, you see that the creators of the Erlang Programming Language didn’t set out to build a functional programming language. That wasn’t what they were after. Rather, they had this specific problem domain they were looking at, telecommunications. Where they had to build software that had:
Was capable of handling high traffic
Was capable of being updated without having to be shut down.
With these three requirements guiding them, they ended up building Erlang. Now, it so happens it does share a lot of Functional Programming stuff but that wasn’t the intent.
I really think it’s all just my faulty memory. But perhaps also there are just more tools to be used. Db stuff etc…
Maybe ppl come from other languages and bring their style with them - if you don’t really know about purity then you don’t take care to produce it (Joe Armstrong and functional people often talk about pushing impurity to the edges of your program etc).
That’s not really true. One of the benefits of purity is that it makes it easy to write concurrent and/or parallelized code. For example, you may evaluate the functions in a set of pure functions in any order and still yield the same value (indeed, this is what evaluation strategies in Control.Parallel.Strategies allow you to do; in this case, it exploits Haskell’s lazy evaluation). Also, message passing is most certainly something you can do in Haskell (e.g., Cloud Haskell).
From the time I wrote that post ten months ago, I’ve learned a lot of new things.
Most importantly, with relation to purity, is that purity is a property of the outside of something. At some point underwater, Haskell’s functions really do force the run time environment to call some ‘impure’ code. But because user code has no access to these impure underwater details, these functions remain pure. Another example would be the ST monad, which allows you to implement impure algorithms like QuickSort in an imperative way, while remaining ‘pure’ to the outside world.
The same is true for message passing: You can either view a process as a system, and say that it breaks purity when it communicates to another process. Or you can view a group of processes as a system, and say that they are pure as long as communication does not go to a process on an external node.
Or you can view your whole node cluster as a system, in which case all message passing can be considered pure.
If I have a set of functions that each send a (different!) message from the current process to another process B, awaiting a reply from this process, which will be a list of all messages this process B has received, then the answer will definitely be different. So this cannot be considered pure from the context of process A. It can be considered pure from outside A+B.
The most important thing remains that writing pure code, is that you will get referential transparency. This is the reason both of the advantages I listed in my earlier post are possible. Code that is referential transparent is easy to understand, reason about and refactor, both by humans and computer programs alike.