Intro
Elixir is strange language, some special things like |> and with
statement can make us confuse but it so convenient if we understand that.
In this topic, I will explain a little of bit about Elixir pipe operator and with
statement.
Pipe operator
Pipe operator has syntax |>
and it has same way with pipe in bash script.
One thing we need to remember in here is first argument of a function (except the first function) in pipe is got from result of function right before |>
syntax. Example:
m = %{}
m |> Map.put(:a, 1)
In this example, m
variable stand before |>
will be the first argument in Map.put
function. In normal code, it looks like: Map.put(m, :a, 1)
Other example:
f = fn -> %{} end
f.() |> Map.put(:a, 1) |> Map.get(:a)
In this example, the first function f.()
looks like normal function. After executed, result will be the first argument in Map.put
function, after Map.put
function was executed, we have result %{a: 1}
. The last function Map.get
will have first argument is %{a: 1}
(from function right before |>
of that), after Map.get
is executed we will get value 1
from key :a
.
Some syntax styles for pipe operator:
# in a line
map = %{} |> Map.put(:a, 1) |> Map.put(:b, 2)
# in multi lines, I recommend this style.
map =
%{}
|> Map.put(:a, 1)
|> Map.put(:b, 2)
# or
map =
%{} |>
Map.put(:a, 1) |>
Map.put(:b, 2)
A downside of pipe is if a function return an expected value or complex value like {:ok, result}
it can raise and a runtime error then we can't use pipe operator. But luckily we have with
statement. Now we go to with
statement!
with statement
with
statement is very interested thing for work with complex result return from a function. The result has format like {:ok, "Hello"}
, {:error, :not_found}
or even with a map or list can work with
statement.
The first thing we need to remember to work with with
in here is if function or expression or a variable (still is an expression) return a result that not match with pattern (I have an topic about pattern if you need) the with
statement will return that result (or go to else
block if we have).
The second thing we need to remember is after all function/expression in with
block before do
work as we expected the code will go to do
block then we can continue with our happy case.
The third thing we need to known is in unexpected case, a pattern is unmatch (not go to do
block) we can process it in else
case. We can use pattern in else
block for known exactly what happen or simply ignore it.
with
statement have way look like pipe operator but help us can check and extract value by pattern for next function/expression.
Example for case all values as we expected:
a_map = %{hello: :world, nested_map: %{"a" => 1, b: 2}}
nested_value =
with %{nested_map: m} <- a_map,
%{"a" => value} <- m do
IO.puts "value in nested map: #{inspect value}"
value
end
Or common case for us like:
f1 = fn(n) ->
{:ok, n}
end
f2 = fn
:default ->
{:ok, 3_000}
n when is_integer(n) ->
{:ok, n}
str when is_bitstring(str) ->
str
_ ->
{:error, :invalid_config}
end
f3 = fn check ->
with {:ok, config} <- f1.(check),
{:ok, value} <- f2.(config) do
IO.puts "valid config, value: #{inspect value}"
else
{:error, reason} ->
IO.puts "invalid config, reason: #{inspect reason}"
unknown ->
IO.puts "unknown result, #{inspect unknown}"
end
end
f3.(:default)
# print:
# valid config, value: 3000
f3.(:a)
# print:
# invalid config, reason: :invalid_config
f3.("default")
# print:
# unknown result, "default"
We can easy work with a multi functions/expressions. we can do match a pattern and extract value from result of function then pass to next function.
Top comments (0)