As explained in this post, I made the switch from a slow Wordpress website to a blazing fast application built with Elixir and Phoenix. Now, I want to share the 10 things I learned about these technologies as a Rubyist.
1. Functional Programming
Elixir is a functional language, which is quite a change when coming from the object-oriented Ruby where everything is an object. But what does it mean exactly?
If you don’t know, functional programming is a programming paradigm. The principal characteristic of this paradigm is the absence of side effects when calling a method. Most features of the Elixir language (immutable data, pattern matching, first-class functions and more) can be derived from the desire to prevent side-effects, as can be expected from a functional language.
Functional Programming is a wide and interesting topic, one I’m not comfortable enough explaining. But if you want to take a closer look, I can recommend this article on Medium.
2. Immutability
The fact that data are immutable is a big change from Ruby. If you add an element to a list in Elixir, you won’t be getting back the same list - a new one will have been created with the added element. However, a variable can be rebinded, which means you can change which data structure the variable points to/references in memory.
So you can do this…
x = %{} # x points to the immutable data %{}
x = %{one: "two"} # x now points to the immutable data %{one: "two"}
…but you cannot do the equivalent of this (written in Ruby):
x = {} # x points to the data {}
x[:one] = "two" # x still points to the same data,
# but the data has been modified
It also means you cannot change data in loops; instead, map
or reduce
should be used. It’s also a good example of how functions don’t have side-effects and won’t change the state of the program.
a = 0
Enum.each [1, 2, 3, 4], fn number ->
# We are not changing a, just creating new variables
# limited to the scope of this anonymous function
a = a + number
end
# Therefore a didn't change
IO.puts a # => 0
This is easily fixed with reduce
- we can tell Elixir to calculate the sum using an accumulator (acc
).
a = Enum.reduce [1, 2, 3, 4], 0, fn number, acc ->
number + acc
end
IO.puts a # => 10
The property of immutability has a number of advantages like data consistency and simpler concurrent programming (no need for locks!). It also makes it easier to reason about the code since functions are just about transforming their inputs into outputs, and not changing state.
3. Pattern Matching
I love pattern matching! I’m still learning all the intricacies, but it’s super useful to implement guard clauses (for example). Whereas in Ruby, you might do something like this:
def foo(a)
return 0 unless a > 0
2.0 / a
end
p foo(0) # 0
p foo(3) # 0.6666666666666666
In Elixir, you can just write:
defmodule Calculator do
def foo(a) when a > 0 do
2 / a
end
def foo(_), do: 0
end
IO.puts Calculator.foo(0) # 0
IO.puts Calculator.foo(3) # 0.6666666666666666
And you can add as many definition as you need. Note that I used _
for the second definition of foo
because otherwise, Elixir would give me a warning for having an unused variable.
It can also be used to implement recursivity. Here is a relatively simple naive implementation of Fibonacci:
defmodule Fibonacci do
def calc(0), do: 0
def calc(1), do: 1
def calc(n) when is_integer(n) and n > 1 do
calc(n - 1) + calc(n - 2)
end
def calc(_), do: {:error, "Only positive integers allowed."}
end
IO.inspect Fibonacci.calc(10) # 55
IO.inspect Fibonacci.calc("foo") # {:error, "Only positive integer allowed."}
We can do all sorts of things with pattern matching. Here, the foo
function is returning a tuple. When using it, I can pattern match with another tuple to extract the values from the tuple.
defmodule Foo do
def foo do
{"foo", "faa"}
end
end
# Thanks to pattern matching
{a, b} = Foo.foo
IO.puts a
IO.puts b
This pattern is often used to catch errors as you can see in the code below, using a case.
case do_something_dangerous() do
{:success, data} -> # process data
{:error, message} -> # process error
end
4. Supervisors and running stuff in the background
If you’re used to working with Ruby on Rails, you know that the right way to run long tasks (like sending a bunch of emails or generating a report) is to create a background job with Sidekiq or Resque.
With Phoenix (thanks to Elixir&Erlang), there is absolutely no need for that - instead,concurrent processes (Elixir processes, not OS processes) can be spawned with 1 line of code (spawn(fn -> 2 * 2 end)
).
In order to manage these processes, Elixir provides the Supervisor module.
When generating a new Phoenix application, you will have the following supervisor configuration:
# lib/devblast.ex
defmodule Devblast do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
supervisor(Devblast.Endpoint, []),
supervisor(Devblast.Repo, []),
# Here you could define other workers and supervisors as children
# worker(Devblast.Worker, [arg1, arg2, arg3]),
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Devblast.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
Devblast.Endpoint.config_change(changed, removed)
:ok
end
end
You can then add more supervisors and use them to run stuff in the background (I added one to handle small tasks like sending email, and another one as a memory cache).
children = [
supervisor(Devblast.Endpoint, []),
supervisor(Devblast.Repo, []),
supervisor(Task.Supervisor, [[name: Devblast.TaskSupervisor]]),
]
Task.Supervisor.start_child Devblast.TaskSupervisor, fn ->
# Run some suff in the background
end
I learned how to do this thanks to Daniel and his great article. Kudos to him!
5. Pipes are awesome
Familiar with *nix commands? Piping commands like ls -a | grep *.ex
? Well, Elixir provides the same feature with the pipe operator.
This means you don’t have to write code like this…
a = [1, [2], 3]
a = List.flatten(a)
a = Enum.map(a, fn x -> x * 2 end)
…or like this:
a = Enum.map(List.flatten([1, [2], 3]), fn x -> x * 2 end)
Instead, you can just pipe the output of each function and send it as the first argument of the following one with the pipe operator!
[1, [2], 3] |> List.flatten |> Enum.map(fn x -> x * 2 end)
This example was taken from the Elixir documentation. I was lacking the imagination to find an example…
6. Phoenix doesn’t have much hidden magic
There is much less magic in Phoenix than there is in Rails. One of the things you really need to understand is the Plug specification. In Phoenix, the entire HTTP layer is handled internally by a bunch of plugs (note that you can easily add your own!)
Here is a pipeline of plugs generated by Phoenix in the router file:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
Each one of these plugs will receive the request object (conn
) and transform it.
7. You need to learn how Brunch works
The Phoenix team decided to use an existing build tool for the assets, which is not a bad idea. They went with brunch.io, which is pretty simple and easy to use, as long as you go through their documentation.
8. Huge lack of tutorials
There is still a huge lack of tutorials and articles on Elixir and Phoenix. I had some issues for which I wasn’t able to find any help, and had to spend a lot of time figuring on my own. I’m planning to write some stuff about them to fill the hole.
9. edeliver and exrm/distillery for deployments?
The commonly accepted way to deploy Phoenix applications is with edeliver and either exrm or distillery. edeliver lets us perform hot-code upgrades which is a cool way to upgrade your application without restarting it.
However, I was disappointed by how slow the deployment was. Maybe I did something wrong, but the generated release would take hours to get copied to the production server…
So instead, I decided to go with Dokku and I’m quite happy with it, even if I don’t have hot-code upgrades!
10. Frameworks should have unique name.
Alright, this last point is a bit of a joke - don’t take it too seriously. I really had this issue with Rails ,but it happened to me more than once with Phoenix: there are too many things named Phoenix!
I can’t tell how often I searched for something in Google using “phoenix” and my query and received results about Apache Phoenix or the city of Phoenix.
Phoenix is not unique enough :’)
The End
I still have many, many things to learn, but I just wanted to share some of the first things I’ve encountered in the past weeks. I will be writing more on my experience with Elixir and Phoenix shortly, so don’t forget to subscribe to the newsletter or the RSS feed.
I’m still a beginner in Elixir so all the information in this article might not be 100% accurate. If you find anything wrong, please leave a comment to let me know.