I have this code:
def edit(conn, params) do
with m1 <- Repo.get(Model1, params["model1_id"]),
m2 <- Repo.get(Model2, params["model2_id"]),
!is_nil(m1) and !is_nil(m2)
do
# 1
res = !is_nil(m1) and !is_nil(m2)
IO.puts("***** res: #{res}") # ===> false
IO.puts("***** m1: #{Kernel.inspect(m1)}") # ===> prints a struct
IO.puts("***** m1 is_nil: #{is_nil(m1)}") # ===> false
IO.puts("***** m2: #{Kernel.inspect(m2)}") # ===> nil
IO.puts("***** m2 is_nil: #{is_nil(m2)}") # ===> true
else
#2
_ -> raise ArgumentError, "not found"
end
end
The flow #1 gets executed even though m2
is nil. How can it be? How to fix it?
Goal - ensure that m1 and m2 aren't nil and then execute the flow #1.
with
The with
expression is used strictly for pattern matching. It is not a "chainable replacement" for if-else
conditionals.
Basically the with
will go through all your clauses and try to pattern match them to the left side of the <-
arrow. It will only execute one of the error clauses when the first pattern match fails (doesn't match).
Your third line in the with
is !is_nil(m1) and !is_nil(m2)
, which always successfully pattern matches even though the expression itself equals to false
.
To make the code do what you actually want you should add a left side to the third line so that it is forced to pattern match:
with m1 <- Repo.get(Model1, params["model1_id"]),
m2 <- Repo.get(Model2, params["model2_id"]),
{false, false} <- {is_nil(m1), is_nil(m2)} do
...
To make the code a bit more idiomatic Elixir, you can also use Guards, which is_nil
is allowed as.
This would make your code look like:
with m1 when not is_nil(m1) <- Repo.get(Model1, params["model1_id"]),
m2 when not is_nil(m2) <- Repo.get(Model2, params["model2_id"]) do
...
One last tip would be to always focus on readability. You are writing your code for humans to read, so less stuff happening on a line is usually easier to read.
Your code would be even more readable as:
m1 = Repo.get(Model1, params["model1_id"])
m2 = Repo.get(Model2, params["model2_id"])
with m1 when not is_nil(m1) <- m1,
m2 when not is_nil(m2) <- m2 do
...
with
?Your with
is doing nothing except making sure m1
and m2
are not nil
. This can easily be done with a case
or if
as well, since you don't actually need any pattern matching here:
m1 = Repo.get(Model1, params["model1_id"])
m2 = Repo.get(Model2, params["model2_id"])
if !is_nil(m1) && !is_nil(m2) do
...