This is a short list of Elixir basics made based on the great official Elixir introduction found at http://elixir-lang.org/getting-started/introduction.html
For insight on what prompted the creation of this document, check out Elixir, your shortcut to the world of Erlang.
Most snippets can be pasted to iex, the REPL (see below), evaluation result
is displayed as comment below, i.e. returned true would be # > true
-
Dynamically typed, immutable data types, loops with recursion
-
Runs on Erlang VM, can call Erlang code with no overhead
-
Parallelism by message passing and memory-isolated green threads ("processes")
-
Variables can be assigned multiple times (unlike in Erlang)
-
iexfor REPL,elixir <file>to evaluate script,elixircis the compiler,mixis the build tool -
Hex is the package repository https://hex.pm/ used by
mix -
File naming convention:
.exfor compiled and.exsfor script files -
#for comment -
IO.puts "print screen",IO.inspect(data_to_dump_on_screen),inspect(data_to_string) -
Parenthesis are optional in unambiguous situations
-
Foo.bar/2means function named "bar" of module "Foo" with arity 2 -
In REPL
h divprints documentation fordiv,h KernelforKernelmodule -
?suffix convention for function names returning boolean, e.g.even?/1instead ofis_even/1 -
No line termination.
;allows joining lines:x = 1; y = x + 1; y == 2 -
Strings UTF-8
"double quote is for strings'single quote is char list<<0, 1, 2>>is byte sequence~s({"json": "string"})for "triple-quote" string, i.e. no"escaping
-
Data structures
-
[]for linked list (prepending is fast[1] ++ []) -
{}for tuple -
:foofor atom/symbol, -
[a: 1, b: 2]for keyword list (equal to[{:a, 1}, {:b 2}]), keys must be atoms, ordered, can have same key twice -
%{:a => 1, :b => 2}for traditional map, any value as key, no ordering, no duplicate keys (last assignment overwrites)- with atom-only keys,
%{a: 1, b: 2}shorthand can be used - pattern-match-able:
%{:k => v} = %{k: 1, k2: 2}; v == 1
- with atom-only keys,
-
Maps and keyword lists implement
Dictinterface -
Structs are extended maps with default values getting the name of their module
defmodule Struct do defstruct foo: "string", bar: true end %Struct{} == %Struct{foo: "string", bar: true} # > true %Struct{foo: "baz"} == %Struct{foo: "baz", bar: true} # > true
with compile-time checks
%Struct{baz: true} # CompileError, unknown keyand can be pattern matched like maps%Struct{foo: var} = %Struct{}; var == "string"- Cloning struct with different defaults (and no new keys) using
structural sharing, use
|pipe:lower = %Struct{}; upper = %Struct{lower | foo: "STRING"}
- Cloning struct with different defaults (and no new keys) using
structural sharing, use
-
-
a..bfor ranges,Enum.map(1..3, fn x -> x * 2 end) == [2, 4, 6], enumerable is a protocol -
add = fn a, b -> a + b endanonymous function, calledadd.(1,2) -
convention
*sizenamed functions constant time,*lengthrequire iteration -
"interpolate #{variable}","concat" <> "string" -
FYI, comparing different data types OK. Valid:
1 < :atom. Precedence from docs. -
Pattern matching
_discard variable (not readable){i, s, a} = {1, "string", :atom}{:ok, result} = {:ok, 13}->result == 13{:ok, result} = {:error, 13}-> exception[head | tail] = [1, 2, 3]->head == 1 and tail == [2, 3]
-
Pin
^does in-place assert:x = 1; {x, ^x} = {2, 1}; x == 2 -
Functions must be defined inside modules, last statement produces return value
defmodule Math do def sum(a, b) do a + b end end Math.sum(1, 2) # > 3
creates
Math.sum/2 -
Erlang code can be accessed by their atomic names
:math.pow(2, 2) # > 4.0
-
def/2for public (visible to other modules),defp/2for private functions -
captured_fn = &Math.sum/2; captured_fn.(1, 2) == 3 -
&(&1 + 1),&nis nth, argument, statement equalsfn x -> x + 1 end- i.e.
&List.flatten(&1, &2)equalsfn(list, tail) -> List.flatten(list, tail) end
- i.e.
-
Default arguments, can also be statements (evaluated on call)
def fun(x \\ IO.puts "hello world") do x end
-
A parallel "process" is started with
spawn, each haspidfor addressing the process. Currentpidread withself().- Message are sent to
pidwithsend, e.g.send self(), {:some "data} receiveblocks and waits for pattern matched messages
pid = spawn fn -> str = receive do {:msg_a, data} -> "Got tuple matching :msg_a with #{data}" "foo" -> "Got string \"foo\"" some_var -> "Caught something else: #{some_var}" after 1000000 -> "timeout, didn't get anything in 1000ms" end IO.puts str end send(pid, {:msg_a, "foo"}) # > Got tuple matching :msg_a with foo
pidcan be named withProcess.register(pid, :name_for_pid)
- Message are sent to
-
Guards
-
In
casecase {1, 2} do {1, x} when x < 3 -> "guard matched #{x}" {1, x} -> "guard passed #{x}" _ -> "match rest" end # > "guard matched 2"
-
In function definition used for dispatching
defmodule Guard do def func(a) when a < 0 do "#{a} less than 0" end def func(a) when a > 0 do "#{a} greater than 0" end end Guard.func(1) # > "1 greater than 0" Guard.func(-1) # > "-1 less than 0" Guard.func(0) # > ** (FunctionClauseError) no function clause matching in Guard.func/1
-
-
condto do if then else, if none match, error raisedcond do 1 + 1 == 3 -> "if" 1 + 1 == 4 -> "else if" true -> "else" end # > "else"
-
do/end:if true do: (expr, expr, ...)same asif true do expr; expr; ... end -
Streamfor lazy enumeration,Enumfor eager -
|>for pipe, prepends operator LHS (left-hand side) to argument list of RHS function# List.delete(list, item) [1, 2] |> List.delete(1) # > [2] [1, 2, 3] |> Enum.map(fn x -> x + 1 end) |> Enum.filter(fn x -> x < 3 end) |> Enum.sum # > 2
- Elixir functions try to keep operated value as functions' first argument to promote pipe operator's usage
-
Protocol (interface in some other languages) defines prototype for implementation
defprotocol Validation do @fallback_to_any true def valid?(data) end defimpl Validation, for: Integer do def valid?(i), do: i > 0 end Validation.valid?(-3) # > false
Fallback for unexpected data types with
@fallback_to_any trueannotation beforedefprotocolprototype allows.defimpl Validation, for: Any, do: (def valid?(i), do: false)
-
forcomprehension is syntactic sugar for enumerationsresult = for x <- [1,2,3], # "generator" y = x + 1, # temporary variables can be used y > 2, # "filter" y < 4, # another filter do: y + 1 # "collectable" # > [4]
Generator is not same as e.g. Python generator (it can still be enumerable lazy stream though). It's just the part producing values.
-
~for sigil, allowing language extensions, defined with function prototypesigil_{identifier}. Predefined sigils include e.g. regexps (sigil_r)"foo" =~ ~r/foo|bar/ -
Status is usually returned as atom in tuple...
File.read("hello") # > {:error, :enoent}
...instead of raising (throwing) an error (exception).
try do raise "fail" rescue e in RuntimeError -> e end # > %RuntimeError{message: "fail"}
Use
defexceptionto create own exception types. Errors are meant for unexpected situations, not for control flow. Process supervision is the general way to protect against worst case situations. -
try/catchcan be used to return values from bad API / nested structure / function callback loop, should not be used unless it's the only feasible option. -
exitsignals supervisor