I’ve been listening to the Elixir Fountain podcast. It’s a pretty great source of interviews with people who have played some part in the rise of the Elixir programming language.

Episode 19 was particularly interesting to me. It’s an interview with Joe Armstrong, co-creator of Erlang. The discussion includes a lot of details about the platform Elixir is built on top of.

There’s one moment in the episode that surprised me. Joe makes a comment about Erlang being “the most object-oriented language.” The same can be said of Elixir.

Wait, what?

Does Elixir even have objects? No. Isn’t Elixir a functional programming language? Yes.

When I first picked up Elixir, I heard some developers comment that it was an easy transition from Ruby because it was similar in some ways. Early on though, I decided I didn’t agree with that. The syntax was Ruby inspired, sure, but with immutability, pattern matching, and recursion everywhere, it felt like a significantly different beast to me.

Then I started to dig into processes more. As I got deeper, Joe’s comment from the podcast culminated in a significant “Ah ha” moment for me. Let’s see if we can gain a deeper understanding of what he meant.

What Is Object-Orientation?

This is a pretty challenging question for developers. We don’t often seem to agree on exactly how to define this style of programming.

The first book I ever read about object-oriented programming explained it from the point of view of C++ and it drilled into me that it was a combination of three traits: encapsulation, inheritance, and polymorphism. That definition has drawn increasing criticism over time.

A likely better source of definitions for the term is the man who coined it, Dr. Alan Kay. There have been attempts to catalog what he has said about object-orientation. My favorite definitions come from an email he wrote to answer this question in 2003. It talks about his original conception, which includes this description:

I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning – it took a while to see how to do messaging in a programming language efficiently enough to be useful).

The email culminates in this definition:

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

You may have noticed that these definitions never mention things like classes or instances. Wikipedia talks a lot about such things when defining object-oriented programming but I view that more as a description of where a lot of languages have gone with the concept. The heart of object-oriented programming doesn’t seem to require such bits, at least according to the man who named the style.

OK, But Where Are the “Objects?”

Playing with the semantics of these definitions is fun and all, but it does seem like we would at least need some objects for this idea of object-oriented programming to apply to us. Elixir doesn’t strictly have anything called objects, but let’s use a quick example to look at what it does have.

We’ll create a script that fetches a page off the web, crudely parses out the title, and prints it. This doesn’t have any practical value. The whole point is to give use some code to examine, in Elixir’s normal context.

We’ll begin by creating the project as normal:

$ mix new --sup title_fetcher * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/title_fetcher.ex * creating test * creating test/test_helper.exs * creating test/title_fetcher_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd title_fetcher mix test Run "mix help" for more commands.

The --sup switch that I added tells Mix to go ahead and give me a standard Supervisor and prepare my application for running by starting said Supervisor . This just gets us going with less effort.

Now, we need a way to fetch the web page so let’s add the HTTPoison library as a dependency of our project. To do that, we open mix.exs and make two small tweaks:

defmodule TitleFetcher.Mixfile do # ... defp deps do [{:httpoison, "~> 0.8.1"}] end end

This first change just adds the dependency.

defmodule TitleFetcher.Mixfile do # ... def application do [applications: [:logger, :httpoison], mod: {TitleFetcher, []}] end # ... end

The other change was to add :httpoison to the list of applications that Elixir will start up for us. An application in this context means something different from the normal computer term. It helps me to think of it as a reusable component.

With those changes in place, we can ask Mix to fetch our dependencies:

$ mix deps.get Running dependency resolution Dependency resolution completed certifi: 0.3.0 hackney: 1.4.10 httpoison: 0.8.1 idna: 1.1.0 mimerl: 1.0.2 ssl_verify_hostname: 1.0.5 * Getting httpoison (Hex package) Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/httpoison-0.8.1.tar) Using locally cached package * Getting hackney (Hex package) Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/hackney-1.4.10.tar) Using locally cached package ...

We’re ready to add our little bit of code. This time we need to open lib/title_fetcher.ex . It contains a commented out line inside the start() function that looks like this:

# worker(TitleFetcher.Worker, [arg1, arg2, arg3]),

We need to change that to run our code:

worker(Task, [&fetch_title/0]),

A Task is a built-in tool for wrapping some code in an Elixir process. Here we’ve instructed it to call the fetch_title() function. That’s the only other bit that we need to add:

defmodule TitleFetcher do # ... defp fetch_title do body = HTTPoison.get!("https://www.noredink.com/") |> Map.get(:body) Regex.run(~r{<title>([^}, body, capture: :all_but_first) |> hd |> IO.puts System.halt end end

This is the code for what we set out to do. The first pipeline retrieves the contents of NoRedInk’s homepage. The second extracts and prints the title of that page. Then there’s a call to System.halt() to shut everything down.

The end result is probably what you expect to see:

$ mix run --no-halt Compiled lib/title_fetcher.ex NoRedInk makes learning grammar fun and easy

The result isn’t what I wanted to show you though. Let’s make this code show us a little about how it did the work. First, we know we had the Task process managing what we were telling it to do, but let’s ask if there were other processes doing stuff:

defmodule TitleFetcher do # ... defp fetch_title do Process.registered |> Enum.sort |> IO.inspect body = HTTPoison.get!("https://www.noredink.com/") |> Map.get(:body) Regex.run(~r{<title>([^}, body, capture: :all_but_first) |> hd |> IO.puts System.halt end end

There’s just one new line at the beginning of the function. All it does is print out all registered processes for us to inspect. Here’s the updated output:

$ mix run --no-halt Compiled lib/title_fetcher.ex [Hex.Registry.ETS, Hex.State, Hex.Supervisor, Logger, Logger.Supervisor, Logger.Watcher, Mix.ProjectStack, Mix.State, Mix.Supervisor, Mix.TasksServer, TitleFetcher.Supervisor, :application_controller, :code_server, :disk_log_server, :disk_log_sup, :elixir_code_server, :elixir_config, :elixir_counter, :elixir_sup, :erl_prim_loader, :error_logger, :file_server_2, :ftp_sup, :global_group, :global_name_server, :hackney_manager, :hackney_sup, :hex_fetcher, :httpc_handler_sup, :httpc_hex, :httpc_manager, :httpc_profile_sup, :httpc_sup, :httpd_sup, :inet_db, :inets_sup, :init, :kernel_safe_sup, :kernel_sup, :rex, :ssl_listen_tracker_sup, :ssl_manager, :ssl_sup, :standard_error, :standard_error_sup, :tftp_sup, :tls_connection_sup, :user] NoRedInk makes learning grammar fun and easy

Wow, there’s kind of a lot going on in there! Most of what we see are things that Elixir setup to get things ready for our code to run.

You may notice that the list doesn’t include any mention of our HTTPoison dependency. That’s because HTTPoison is a thin Elixirifying wrapper over an Erlang HTTP client library called hackney. If you reexamine the list above you will find a couple of hackney processes, including :hackney_manager .

We’ve reached the key idea of how Elixir projects work.

Inside our Task , we called a function to fetch the page content: HTTPoison.get!() . That executed in our process. But we now have strong reason to believe that a separate hackney process is what actually did the fetching over the wires. If that’s true, these processes must have sent some messages to each other under the hood of these seemingly simple function calls.

Let’s make another debugging change to confirm our hunch:

defmodule TitleFetcher do # ... defp fetch_title do :sys.trace(:hackney_manager, true) body = HTTPoison.get!("https://www.noredink.com/") |> Map.get(:body) Regex.run(~r{<title>([^}, body, capture: :all_but_first) |> hd |> IO.puts System.halt end end

This time the line added turns on a debugging feature of the OTP (the framework our code is running on top of). We’ve asked for a message trace() of that :hackney_manager process that we found earlier. Observe what that reveals:

$ mix run --no-halt Compiled lib/title_fetcher.ex *DBG* hackney_manager got call {new_request,,#Ref, {client,undefined,hackney_dummy_metrics, hackney_ssl_transport, "www.noredink.com",443, >,[],nil,nil,nil, true,hackney_pool,5000,false,5,false,5, nil,nil,nil,undefined,start,nil,normal, false,false,false,undefined,false,nil, waiting,nil,4096,>,[],undefined,nil, nil,nil,nil,undefined,nil}} from *DBG* hackney_manager sent {ok,{1457,541907,594514}} to , new state {...} NoRedInk makes learning grammar fun and easy *DBG* hackney_manager got cast {cancel_request,#Ref}

Bingo. The :hackney_manager process did indeed receive a message asking it to grab the web page we wanted to load. You don’t see it here, but hackney eventually sent a message back to the requester containing the page content.

This is how Elixir works. Concerns are divided up into processes that communicate by sending messages to each other. These processes are the “objects” of this world. They are what we should be judging to evaluate Elixir’s object-oriented merits.

Get to the Score Already!

We can assess how Elixir stacks up as an object-oriented language point by point using all of the definitions mentioned earlier. First up is the idea of “biological cells… only able to communicate with messages.” I hope the example above has shown that this is how you get things done in Elixir. In several other languages, objects are the recommended design tool, but in Elixir processes are essential.

What about the idea of “local retention and protection and hiding of state-process,” which we often call encapsulation? To put it bluntly, it’s enforced. Processes share nothing. Outside of the debugging tools, this is just the law of the land in Elixir.

Inheritance? No. Elixir doesn’t have this concept. It’s possible to build your own version on top of processes, but nothing is provided. This might be another plus.

There’s one last point: “extreme late-binding of all things.” We have tried to shorten this idea to polymorphism, but that’s not actually the same thing. Let’s sort this out with another example.

We’re just going to play around in Elixir’s REPL ( iex ) this time. Let’s ask Elixir to print something to the screen:

iex(1)> IO.puts("Printing to the terminal.") Printing to the terminal. :ok

That looks pretty straightforward, but we want to know how it works.

IO.puts() takes another argument that we got a default for above. Let’s add that in:

iex(2)> IO.puts(Process.group_leader, "More explicit.") More explicit. :ok

This version says that we want to send the output to the group_leader() of our process. That’s the same thing the default did for us in the earlier example. In the case of our REPL session, the group_leader() is essentially pointing at STDOUT . But what does group_leader() really return, in terms of Elixir data types?

iex(3)> pid = Process.group_leader #PID iex(4)> IO.puts(pid, "It's processes all the way down!") It's processes all the way down! :ok

It you’ve been paying attention, I suspect you’re not at all surprised to see that this is another process. Why is that the case though, really?

The phrase “extreme late-binding of all things” is a way of saying something like, “I want the computer to sort out what code to actually run as the need arises.” In some language that might mean two different kind of objects could have print() methods and the computer decides which one to invoke based on which object the message is sent to.

In Elixir, this kind of late dispatch is simply sending messages to some kind of process that understands them. If we swap out that process with another one that understands the same messages, we can change what code is run. For example, we could capture the output from IO.puts() :

iex(5)> {:ok, fake_terminal} = StringIO.open("") {:ok, #PID} iex(6)> IO.puts(fake_terminal, "Where does this go?") :ok iex(7)> {inputs, outputs} = StringIO.contents(fake_terminal) {"", "Where does this go?

"} iex(8)> outputs "Where does this go?

"

StringIO.open() creates a new process that pretends to be an I/O device. It understands the messages they receive and reply with, but it can substitute input from a string and capture output written into a separate string. This is possible because Elixir doesn’t care how the process works internally. It just delivers the messages to it. What code to run is sorted out inside the process itself as it receives the messages.

For a language we don’t traditionally think of as object-oriented, Elixir scores surprisingly well against a few different definitions. Processes make darn good objects!

Does This Matter?

Is it actually significant which language we decide to crown as “The Most Object-Oriented?” Of course not. It can be interesting to consider the parallels though.

If you shift your focus just right when looking at Elixir code, you can see the objects pop out. They’re a little different in this world. However, and this is the neat bit if you ask me, your object-oriented design skills (assuming you studied the good stuff, like Practical Object-Oriented Design in Ruby) still very much apply. I didn’t expect that and it’s a nice surprise.



James Edward Gray II

@JEG2

Engineer at NoRedInk