First, here's a simple example.
The executor just receives the code which should be executed and calls the handle/1 function in client.erl. So it does:
"Value = Hello"
So what I noticed here is that the compile-time dependency exists only for the client.erl while not for the executor.erl. Another nice point here, is that Mod:handle(Val) compiles without knowing actually what is Mod, i.e. erlang is quite a dynamic language?
Next, I took an example code from the book, and simplified it a bit, in order to get to the point.
Executor is now a process, which sends messages to itself, and also executing the code for a given module via handle/1. Here's the execution result:
What I would like to emphasize here, is:
- The syntax for spawning a process: spawn(fun() -> some_function() end)
- The syntax for sending the messages: Pid ! Message
- The syntax for receiving the messages: receive Pattern1 -> Ex1; Pattern2 -> Ex2; ... end
- Tail recursion: note that the executor's loop/2 function recursively calls itself at the end of the body. This kind of coding practice allows to write recursive code without consuming the stack, i.e. this call is transformed into a normal loop.
- Modularity. In the example one can see, that executor.erl is responsible for the infrastructural functionality, e.g. process spawn, message sending, etc. On the other hand, client.erl doesn't know anything about the infrastructure but just implements the "business logic" in handle/2 function. This kind of code separation allows to proceed with hot code swap in the further example.
In the example code above, executor.erl is complemented with a new function swap_code/2, which is used to call the loop/2 function while providing it a new module for Mod. Also, loop/2 is complemented with a block that is actually making the switch to the new client module. The client1.erl module is just a copy of client.erl with just slight changes made to the response logic in handle/1.
Here's the execution result:
So that said, we started the executor module first, then executed the client.erl module's code and after that we switched the functionality to the brand new client1.erl ONLINE, whitout restarting the executor!
The book example used a dictionary to save the state of the application, but I removed it just to simplify the code and get to the point more easily.
I'm astonished that erlang provides such functionality out-of-the-box: concise syntax, hot code swap, built-in process distribution, i.e. the executor process in the example could have been started on a separate node or even on the other host! This is a nice language and a great technology. Probably this explains why Scala (which is inspired from erlang) has the momentum now.