Concurrency, Processes and Recursion
We finally got into the number five! First of all, sorry for taking so long to publish this article, a bunch of things happened at once, I had some health issues plus some conferences I had to speak at, but I come with good news, the series is back, and I hope to keep the rate of 1 article a week from now on.
In this article we will talk about Concurrency, Processes and Recursion, it’s time to start taking some traction and put some of the stuff we saw on the lastest articles together.
If you like the series, please let me know on twitter! And if you haven’t checked the other episodes yet you can do it following the links bellow:
- Episode I - Elixir, Pipe Operator and Pattern Matching
- Episode II - Actor Model, Modules and functions
- Episode III - Maps, Functions + Pattern Matching = ❤️
- Episode IV - Elixir Types, Data Structures and Underscore
- Episode V - Concurrency, Processes and Recursion
- Episode VI - Head + Tail and List Comprehension
- Episode VII - Processes Communication
Concurrency & Processes
We have discussed concurrency in the other articles already, how the Erlang VM enables you to take advantage of concurrency in such a graceful and powerful way, and that’s only possible thanks to the actor model and the way the VM handles the processes and its communications (for further info around Actor Model check Episode II - Actor Model, Modules and functions).
Processes started and executed on the Erlang VM are completely different from the usual processes you see on your operation system, it’s really lightweight, and the VM can run a bunch of them at the same time, thanks to the separation between all processes and the functional paradigm, that allow us to avoid race conditions and to actually have concurrent code safely.
Every time you run any elixir code, from executing a script to running a web application, a new process is started, and this process can also spawn other processes.
A process can be as little as a single function, but others can be really complex and even coordinate other processes, as the ones known as supervisors, we’ll talk about those in future articles.
Let’s start by checking some of the modules and functions that can create new processes, the easiest and simplest one is probably the
spawn function, a function from the Erlang standard library, let’s check how it works:
# Starting a new process can be really simple, one of the # simplest ways is by using the `spawn/3` function. # (`/3` specify that it expects 3 arguments) defmodule SpawnExample do # This is the main funciton of this module, # the one that starts our application. def start do IO.puts "============= Starting Application" # We call a function that is responsible for # spawning our new process spawn_proc end # This is the function the new process is supposed to execute. # Once the process executes this function it will # die automatically. def proc_func do IO.puts "* Started Process" # Wait for 1 second, for the sake of simulating some computing. :timer.sleep(1000) IO.puts "* Finished Process" end # `spawn_proc/0` is our private function (that's why we use `defp`) # responsible for spawning a new process. defp spawn_proc do # We spawn a new process, passing the module and the # fucntion this process is supposed to execute. # The `__MODULE__` syntax is a reference to this own module # and the atom `:proc_func` indicates the function inside this # module that should be executed. # We are also sending an empty list as third argument, indicating # that this `proc_func` method does not expect any arguments. spawn(__MODULE__, :proc_func, ) IO.puts "============= Finishing Application" end end # Let's call on main method and see what the output will be. SpawnExamle.start # ============= Starting Application # ============= Finishing Application # * Started Process # :ok # * Finished Process # # Our application is executed so fast the it's finished # before our new process is even able to print "* Started Process". # Our new process is started in parallel and finishes only after our # main application is already done.
That’s cool, it doesn’t look so awesome yet, but trust me it gets way better once we start multiple processes, but to do it we’ll need to dive into our next topic on this article, recursion, not only what it is but also how to implement recursion on Elixir by bringing pattern matching into the play (further info around patter matching on Episode I - Elixir, Pipe Operator and Pattern Matching and Episode III - Maps, Functions + Pattern Matching = ❤️).
Recursion can be tricky to understand when using functional languages. Because of immutability you don’t have the usual tools you are used to, like
while, so in order to achieve recursion you need to bring other elements, like pattern matching.
We have already implemented functions with pattern matching in other episodes, but lets check how we could use it to achieve recursion and compare it with what we would expect to have in ruby.
Let’s suppose we want to loop through a list, printing its elements and then a finish message, pretty simple right? Let’s check it on ruby:
Our class definition class RecursionExample # usual initialize method, it receives and store # the list when instantiating the object. def initialize(list) @list = list end # this is the method that actually loops through # the list def print_list # by usin the `each` method we can easily loop # through the list while using `puts` to print its elements @list.each do |element| puts element end # the final message indicating the execution # has finished. puts "Finished list" end end re = RecursionExample.new([1,2,3,4,5]) re.print_list # As you can see it will correctly loop through # the elements on the list printing each one. # Everything done following usual OOP style. # # 1 # 2 # 3 # 4 # 5 # Finished list
Now let’s check how we would tackle it within elixir, in a functional way, without tools like
# Our module definition defmodule RecursionExample do # This is our main function, responsible for # receiving a list, print its first element, # then call itself again passing the rest of the # list as the new argument. # # This is accomplished thanks to the [head | tail] # implementation, another Erlang feature that basically # enables you to separate the first element from the # rest of a list. We will go back to it later. # def print_list([first_element | rest_of_the_list]) do # Basically prints the first element of the list IO.puts first_element # Calls itself again, now passing what left over # the list as the argument print_list(rest_of_the_list) end # Here we see a function with the same name, but by # using pattern matching we know that it'll only # be called when an empty list is passed as argument, # therefore this will be matched by call on the function # above, when the all elements of the list were printed. # def print_list() do # Prints the message indicating the execution # has finished IO.puts "Finished list" end end RecursionExample.print_list([1,2,3,4,5]) # Here we are able to achieve the same result in # a functional way. # You can see wee have two functions with the same name, # as we have done in the past, we are using pattern matching # to define which function should be called. # # 1 # 2 # 3 # 4 # 5 # Finished list
That’s awesome, by using the same core concepts we seen on last episodes we can easily achieve recursion, and do it in a graceful and clear way, by having different functions. This can make debugging so much easier and also feels really organized.
Now let’s go for the real deal, we want to adapt our initial example used to spawn a new processes, now we want it to support an infinite number of processes (as many as your computer can handle *hint: it’s a freaking lot).
Now that we know how to use recursion it shouldn’t be that hard, let’s check the result:
defmodule SpawnExample do def start do IO.puts "============= Starting Application" # Now our `spawn_proc/1` function receives one argument # the number of processes it's supposed to spawn. spawn_proc(10) end def proc_func do IO.puts "* Started Process" :timer.sleep(1000) IO.puts "* Finished Process" end # Now we will use pattern matching to implement recursion # into our application, this can be mind bending if # you haven't read the previous articles. # # This basically means that this signature of `spawn_proc/1` # will be matched whenever the argument sent to it is `0` # defp spawn_proc(0) do IO.puts "============= Finishing Application" end # As you might have noticed we have two methods with the same # name but different signatures, despite of both getting # one argument, by using pattern matching we can be sure that # unless the argument is `0` this is the function that will # be executed # defp spawn_proc(number_of_procs) do spawn(__MODULE__, :proc_func, ) # After spawning the new process, we decrease the # `number_of_procs` and call this same method again, now with # `number_of_procs - 1` as its argument, this is a great # example of concurrency + recursion + patter matching. # When `number_of_procs - 1` is equal `0` the first `spawn_proc/1` # function will be called, finishing the application. # procs_missing = number_of_procs - 1 spawn_proc(procs_missing) end end # Let's start our application and check the output SpawnExample.start # Again you can see our application being executed so fast # that it finishes before our new processes are able to print # its initial message. # All 10 processes are started concurrently this time. # # ============= Starting Application # ============= Finishing Application # * Started Process # * Started Process # * Started Process # * Started Process # * Started Process # * Started Process # * Started Process # * Started Process # * Started Process # * Started Process # :ok # * Finished Process # * Finished Process # * Finished Process # * Finished Process # * Finished Process # * Finished Process # * Finished Process # * Finished Process # * Finished Process # * Finished Process
That’s awesome, now we already know how to spawn a new processes, and use recursion, this is a great case for when working with scripts, and it does takes advantage of some of the main Erlang and Elixir features.
This is episode V of Elixir with a Rubyist!!! I feel great about this series and really happy to be publishing it again! I’m looking forward for the next episodes, it feels we are starting to get into the fun stuff.
This is a series of short bar-like conversations around Elixir, it’s aimed to help developers that are trying to understand and wrap their heads around it.
On the next episodes we will finally get into the
Task module, also discuss
[head | tail], and the fun stuff. If you liked please let me know on the comments bellow and over twitter.