Elixir
Erlang was proprietary software within Ericsson, which is a telecom company. The software was maintained by the Open Telecom Platform (OTP) product unit, who released Erlang/OTP as open-source in the late 1990s.
The release comes with many handy tools and libraries for development…
- Erlang compiler;
- Test framework;
- Profiler;
- (etc.)
Variables, Function Names, Module Attributes, etc.
snake_case
is enforced.Atoms
snake_case
is recommended, or PascalCase
is also acceptable.Module Names and Aliases
PascalCase
is enforced.(In fact, the aliases are converted to atoms during compilation, because the modules are always represented by atoms in the Erlang VM.)
Filenames
snake_case
is recommended.Variables
A value that will not be used must be assigned to
_
or to a variable starting with underscore (_foo
).Function Names
If the function name starts with underscore, the function will not be imported by default.
The trailing bang signifies a function or macro where failure cases raise an exception.
The version without
!
is preferred when you want to handle different outcomes using pattern matching.However, if you expect the outcome to always be successful, the bang variation is more convenient because it raises a more helpful error message on failure.
Functions, except those who are also valid guards, that return a boolean are named with a trailing question mark.
Type checks and other boolean checks that are allowed in guard clauses are named with the
is_
prefix, instead of a trailing question mark.length and size
If the function name contains
size
, the operation runs in O(1) time.If the function name contains
length
, the operation runs in O(n) time.Lists are stored in memory as linked lists. This means accessing the length of a list is a linear operation.
Tuples are stored contiguously in memory. Getting the tuple size or accessing an element by index is fast. However, updating or adding elements to tuples is expensive because it requires creating a new tuple in memory.
Keyword list represents the key-value pairs in a list of 2-item tuples, where the key is always an atom.
## Since the usage of keyword lists is very common
[{:a, 1}, {:b, 2}]
## can be shortened as
[a: 1, b: 2]
When the keyword list is the last argument of a function, the square brackets are optional.
## Therefore
if false, [do: :this, else: :that]
## is equivalent to
if false, do: :this, else: :that
Atoms are similar to enum. They are mapped to distinct integers behind the scene.
- Comparing atoms is more efficient than comparing string;
- Declaring the same atom multiple times won’t consume more memory;
- Using atoms (instead of integers) improves code readability.
case {1, 2, 3} do
{4, 5, 6} ->
"This clause won't match"
{1, x, 3} ->
"This clause will match and bind x to 2 in this clause"
_ ->
"This clause would match any value"
end
n = 10
cond do
n % 3 == 0 ->
"n is a multiple of 3"
n % 2 == 0 ->
"n is a even number"
true ->
"n is none of the above"
end
with {:ok, date} <- next_sunday(),
{:ok, weather} <- weather_forecast(date) do
IO.puts("Next sunday is a #{weather} day")
else
{:error, :date_out_of_range} ->
IO.puts("Cannot obtain weather forecast")
error ->
IO.puts("Unknown error: #{inspect(error)}")
end
f = fn
x, y when x > 0 -> x + y
x, y -> x * y
end
## [NOTE]
## The number of arguments in each clause needs to be the same, otherwise an error is raised.
This enables the application to store state as well. Below is an example implementation of a simple key-value store.
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
Because the compiler will apply the Tail Call Optimization (TCO).
When the function calls itself as the final statement, instead of adding a new stack frame to the call stack, it does a
goto
.We
import
a module to access its functions and macros without referencing its fully-qualified name.⚠️ The use ofimport
is discouraged because you cannot directly see in what module a function is defined.
We
require
a module to opt-in using the macros.We
use
a module to allow the module to inject any code in the current module.💡 Using a module is the same asrequir
ing it and then calling its__using__
function.
Just another name for “middleware”. There are two implementations.
The Function Implementation
It takes the
Plug.Conn
struct, optionally modifies it, and returns it.def hello_world_plug(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello world")
end
The Module Implementation
The
init/1
function initialises the options.The
call/2
function takes the Plug.Conn
struct, optionally modifies it, and returns it.defmodule MyPlug do
def init([]), do: false
def call(conn, _opts), do: conn
end
Last modified 1yr ago