As we stated at the beginning of this chapter, Elixir targets the Erlang runtime. Elixir is compiled to byte-code that can run on an Erlang VM (or BEAM), and Erlang libraries can be used in your Elixir projects (and vice versa). The philosophy in the Elixir community is to not reinvent the wheel and directly use Erlang libraries when appropriate. The creation of Elixir libraries that simply wrap an underlying Erlang library is discouraged, as you can directly call an Erlang library from your Elixir code.
We can take advantage not only of Erlang libraries, but also of their tooling. For instance, Erlang ships with a tool called Observer, which allows you to monitor your server and provides you with tons of useful information about the Erlang VM, such as the running processes or dynamic charts of load. We'll explore this tool in greater detail in Chapter 11, Keeping an Eye on Your Processes, when we talk about monitoring.
To utilize an Erlang library, you write its name as an atom and then call functions on it. Elixir doesn't have a Math module, so when more advanced mathematical operators are needed, it's common to call the math Erlang library. Let's use this library to calculate the natural logarithm of 10:
When we introduced the data types in Elixir, we mentioned the Port type, which is a reference to a resource used by the Erlang VM to interact with external resources. Let's now see how we can use them to interact with an operating system process. To interact with ports, we use functions from the Port module. In the following example, we'll use the whoami UNIX command, which prints the username associated with the current user. To do that, we open a port and provide the name of the executable we want to run:
iex> port = Port.open({:spawn, "whoami"}, [:binary]) #Port<0.3730>
We passed the [:binary] option so that we get our result as a binary instead of a list of bytes. We now use the IEx flush() helper to print the messages received by the port:
As our operating system process died after returning its result, the port is also closed. If this was not the case, we could use the Port.close/1 function to explicitly close the port. Also note that this was a very simple example. You can have more complex interactions by using the Kernel.send/2 function to dynamically send messages (that contain commands) to your port. In the official documentation for ports (which is available at https://hexdocs.pm/elixir/Port.html), you can see how this can be achieved.
If all we're interested in is running an operating-system command and getting its result back, we can use the System.cmd/3 function, which is an abstraction on top of ports that allows us to achieve this effortlessly.