Functional and concurrent programming with Erlang - Introduction
I asked, you answered - here we go: From now on I will write a series of posts to document my learning progress about functional and concurrent programming in Erlang. This will be a mix between a tutorial and my own thoughts on the whole topic. I’m really excited for it and I hope you’re too. So let’s get started:
Erlang is a general purpose programming language. It is also concurrent, functional and was made to build high-reliability systems. When one talks about Erlang they usually don’t just mean the language but also the runtime system and libraries that it gets delivered with. This bundle is called Erlang/OTP. OTP stands for Open Telecom Platform (Erlang was initially created to build telephony applications. Nowadays it can be used for many different things and is a popular choice for writing middleware.)
I’m sure one can hold very detailed talks about what functional programming is or isn’t and maybe we’ll do that in the future, but for now, since we’re trying to take our first steps here, we’ll talk about what this means for us from a pragmatic perspective. The languages I have some experience in are all imperative, procedural or object-oriented and I think that’s the case for most of the programmers who want to learn functional programming. One core concept of functional programming is referential transparency. Sounds fancy and complicated at first, but it isn’t. Here’s how it works:
An expression is referentially transparent if it can be replaced with its value without changing the behavior of a program.
This means that every expression and therefore every function and every program need to return the same value for the same input every single time. It also requires that functions don’t have any side-effects, like changing the state of a global variable or of any data structure outside of the local scope of the function itself. Functions which are compliant to these rules are called pure functions. For math that’s always true. You can easily replace 2 + 5 with 7 or 2² with 4 without fearing that your program will break. In imperative languages like C that’s not the case though. myArray might return 5 now. But if I decide to subtract 1 from every number in myArray the same function with the same parameter (myArray) will return 4 instead of 5. So we can’t simply replace myArray with 5, we have to look up the value of myArray every single time.
To guarantee referential transparency functional programming requires single assignment and immutable data structures.
Single assignment: Once a value is bound to a variable the value of this variable will never change (within the given context).
So in strictly functional programming languages you can’t do something like this:
This is common in imperative languages but back in school we learned that ‘x = 1’ means ‘x is 1’ and not ‘x is 1 and sometimes something else and if I program in Ruby x can be an object in 5 minutes’.
Immutable data: Changing a data structure is not possible. Calling a function that would destroy the current state of an existing data structure will instead create a new, updated data structure.
Here’s an example:
a = [1, 2, 3, 4];
b = a.deleteAt(0);
In imperative languages ‘b’ would now point to the same list as ‘a’ and that list would looke like this: [2, 3, 4].
In strictly functional programming languages ‘a’ still points to [1, 2, 3, 4] (since we aren’t allowed to change the existing data structure) and ‘b’ points to the newly created list [2, 3, 4].
Why do we want referential transparency?
Sounds like referential transparency makes things unnecessarily complicated without any good reason? Let me explain the advantages of programming in this functional style:
If we agree to theses terms of service of functional programming, the Erlang VM or any runtime system for any functional programming language we program in can use tricks to make our programs more effective:
- Because our functions are pure they only need to be calculated once for every parameter we put in. If myFunc(5) returns 42 every occurrence of myFunc(5) in our program can be replaced with 42 without the need to calculate it again. This is especially useful if myFunc() is a heavyweight calculation. We don’t have to save the value of myFunc(5) and carry it along through the code just to avoid calling this function again.
- Since no function causes any side-effects and no existing data structure will get manipulated we can easily distribute calculations to many cores or even to other computers. There’s no need for mutexes or any locks on data structures in global shared memory.
- Because of argument number 2 our programs scale very well with an increasing amount of cores. And we don’t even have to do anything after we wrote the program. Simply increasing the amount of cores will make our program run faster.
- Deadlocks are still possible but less likely now because we don’t have to manage all of these locks. Our productivity increases aswell since we don’t have to check our code a bazillion times just to be sure that all the locking and unlocking happens in exactly the right order.
- Locks suck and you know it.
This concludes my first post on this topic. It’s a lot of text and quite theoretical but I wanted to assure that we are on the same page in terms of terminology and our understanding of what functional programming is.
The next part will focus on Erlang and its basic data types, some core principles of the language and how it realizes the ideas of functional programming.