A lot of language support multiple programming paradigms
e.g. Scala supports imperative, functional, object oriented
A lot of languages support some functional features
e.g. High order functions are used in many languages
Tasks with a lot of mutations are not appropriate for FP
Matrix operations is a good example
Matching a source expression with a target expression
=
means "match", not "assignment"iex(1)> a = 1
1
iex(2)> a
1
iex(3)> 1 = a
1
iex(4)> 1 = 1
1
iex(5)> 2 = a
** (MatchError) no match of right hand side value: 1
iex(1)> a = 1
1
iex(2)> [x] = [2]
[2]
iex(3)> x
2
iex(4)> %{key: value} = %{key: "value", other: "other"}
%{key: "value", other: "other"}
iex(5)> value
"value"
Semantic changes depending on the context
# match operator
def match_op_match_context(value) do
{a, b} = value
a + b
end
# case expression
def case_match_conext(value) do
case value do
{a, b} -> a + b
end
end
# function clause
def func_match_context({a, b}), do: a + b
if
/else
case x < 1 do
true -> "if branch"
false -> "else branch"
end
Using predicates when pattern matching, replacing the need for else if
def status(age) when age < 18, do: "child"
def status(age) when age < 65, do: "adult"
def status(age), do: "senior"
Check docs for usable expressions
The following is a common pattern
case value do
a when a == some_variable -> do_something()
_ -> do_something_else()
end
Elixir allow to write it as
case value do
^some_variable -> do_something()
_ -> do_something_else()
end
Match patterns are evaluated simultaneously
therefore the following will not work
def published?(platform, %{^platform => %{state: "published"}}), do: true
def published?(_platform, _info), do: false
Recursive functions usually have a
sum
function0
current_value + sum(rest_of_the_list)
which translates into
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
sum
functionThe result of the recursive call is not enough to compute the function final result
sum([1, 2, 3]) 1 + sum([2, 3]) 1 + 2 + sum([3]) 1 + 2 + 3 + sum([]) 1 + 2 + 3 + 0 1 + 2 + 3 1 + 5 6
Stack increases at each iteration
Make the recursive call the last expression of the function
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
def sum(list), do: do_sum(list, 0)
defp do_sum([], acc), do: acc
defp do_sum([head | tail], acc), do: do_sum(tail, acc + head)
Function calls normally accumulate on the stack
Tail calls optimization avoids increasing the size of the stack when
a function call is a tail call
defmodule WithStack do
def foo(), do: "foo" <> bar()
def bar(), do: "bar" <> baz()
def baz(), do: raise "error"
end
try do
WithStack.foo()
rescue
_ -> :erlang.get_stacktrace()
end
# [{WithStack, :baz, 0, [file: 'iex', line: 6]},
# {WithStack, :bar, 0, [file: 'iex', line: 5]},
# {WithStack, :foo, 0, [file: 'iex', line: 4]},
# {:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 670]},
# ...
# ]
defmodule NoStack do
def foo(), do: bar()
def bar(), do: baz()
def baz(), do: raise "error"
end
try do
NoStack.foo()
rescue
_ -> :erlang.get_stacktrace()
end
# [{NoStack, :baz, 0, [file: 'iex', line: 17]},
# {:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 670]},
# ...
# ]
The following code will not stack overflow
def sample_infinite_loop() do
wait_for_something()
do_something()
sample_infinite_loop()
end
We want to abstract common patterns
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
def count([]), do: 0
def count([head | tail]), do: 1 + sum(tail)
sum
and count
both:This is often called reduce
or fold
Functions can be used as normal values
Many languages implement this feature
The reduce function could be implemented as follow
def reduce([], initial_value, _f), do: initial_value
def reduce([head | tail], initial_value, f) do
f.(head, reduce(tail, initial_value, f))
end
sum
and count
can then be implemented as
def sum(list), do: reduce(list, 0, fn (elem, acc) -> elem + acc end)
def count(list), do: reduce(list, 0, fn (_elem, acc) -> 1 + acc end)
map
: applies a function on all elements of a listfilter
: filters a list using a predicate functionreduce
: reduces a list to a scalar using a binary functionAll data structures in Elixir are immutable
list = [1, 2, 3, 4]
List.delete(list, 3)# returns a new list
map = %{foo: "bar"}
Map.put(map, :bar, "baz") # returns a new map
conn = get_conn()
halt(conn) # returns a new conn
When getting started, is easy to forget about immutability
def some_plug(conn) do
unless authorized?(conn) do
conn
|> put_status(403)
|> halt()
end
conn
end
Agent
when you need stateAgent
is a wrapper around GenServer
GenServer
?spawn
send
receive
monitor
link
pid = spawn fn ->
IO.puts "Hello from #{inspect(self())}"
end
:timer.sleep(10)
Process.alive?(pid) |> IO.inspect # false
pid = spawn fn ->
receive do
{:ping, sender} -> send(sender, :pong)
end
end
:timer.sleep(10)
Process.alive?(pid) # true
send(pid, {:ping, self()})
flush() # :pong
Process.alive?(pid) # false
defmodule PingServer do
def start do
spawn(__MODULE__, :loop, [])
end
def loop do
receive do
{:ping, sender} ->
send(sender, :pong)
loop()
end
end
end
def loop(some_int) do
receive do
{:add, value} ->
loop(some_int + value)
end
end
Stack
serverdefmodule StackServer do
def loop(state) do
receive do
{:push, value} ->
new_state = [value | state]
loop(new_state)
{:pop, sender} ->
[value | new_state] = state
send(sender, value)
loop(new_state)
end
end
end
push
functiondefmodule StackServer do
def push(pid, value) do
send(pid, {:push, value})
end
end
pop
synchronousdefmodule StackServer do
def pop(pid) do
ref = make_ref()
send(pid, {:pop, self(), ref})
receive do
{^ref, value} -> value
end
end
def loop(state) do
receive do
{:pop, sender, ref} ->
[value | new_state] = state
send(sender, {ref, value})
loop(new_state)
end
end
end
defmodule ClumsyGenServer do
def loop(module, state) do
receive do
{:async, message} ->
{:noreply, new_state} = module.handle_cast(message, state)
loop(module, new_state)
{:sync, message, sender, ref} ->
{:reply, reply, new_state} =
module.handle_call(message, {sender, ref}, state)
send(sender, {ref, reply})
loop(module, new_state)
end
end
end
push
and pop
defmodule ClumsyGenServer do
def cast(pid, message) do
send(pid, {:async, message})
end
def call(pid, message) do
ref = make_ref()
send(pid, {:sync, message, self(), ref})
receive do
{^ref, reply} -> reply
end
end
end
push
and pop
defmodule StackServer do
def init(state) do
{:ok, state}
end
def pop(pid) do
ClumsyGenServer.call(pid, :pop)
end
def push(pid, value) do
ClumsyGenServer.cast(pid, {:push, value})
end
def handle_cast({:push, value}, state) do
{:noreply, [value | state]}
end
def handle_call(:pop, _from, [head | rest]) do
{:reply, head, rest}
end
end