Reputation: 2625
I am new to elixir, having hard time with updating the variables. Need some help. I have two Maps
firstMsg = %{msg: "Hello", vt: %{"p1" => 1, "p2" => 1, "p3" => 1}, from: "p3"}
state = %{ :name => "p2",
vector: %{"p1" => 0, "p2" => 0, "p3" => 0},
participants: ["p1","p3","p2"]
}
I am passing these two maps in a function, which should return me either true or false, depending on some conditions.
defmodule Testfunc do
def keep_in_pending(firstMsg, state) do
if (firstMsg.vt[firstMsg.from] == state.vector[firstMsg.from] + 1) do
#IO.puts("Origin proc clock is 1 step ahead from rcvd process Origin clk")
checking = false #initially set this to false
for n <- state.participants do
if n != firstMsg.from do #filter the origin processes
IO.puts("#{n}: #{inspect firstMsg.vt[n]} <= #{n}: #{inspect state.vector[n]} ")
checking = cond do
(firstMsg.vt[n] <= state.vector[n]) -> false
(firstMsg.vt[n] > state.vector[n]) -> true
end
end
end
end
checking
end
end
out = Testfunc.keep_in_pending(firstMsg, state)
IO.puts("#{inspect out}")
It always gives me false (value that I initially assigned to it), and doesn't updates. I think the scope of variable is restricted to the inner "if". Can anyone give me suggestion on how to re arrange this code so that it returns me proper updated boolean value ?
So in this case it should return me true because firstMsg.vt["p1"] > state.vector["p1"].
Upvotes: 2
Views: 1411
Reputation: 2267
Welcome to Elixir. You're right, it is a matter of scope, but it runs a bit deeper than that. Elixir is a language where your data is immutable. You can't set checked
to false, run a loop, and set it to true somewhere in that loop. That would mutate checked
. It's not that someone designed devilish scope rules to prevent this, but rather that the underlying virtual machine doesn't mutate state.
The style of programming where you set some state, then run a procedure that changes that state, relies on mutable state. When state is immutable, the alternative to a loop is instead recursion. You carry new state in every recursive call.
You're learning a functional language, and I think it will be helpful to pull apart your code into a few functions. This will both address your immediate concern, and make your code easier to understand.
def keep_in_pending(%{from: from, vt: vt}, %{vector: vector, participants: ps}) do
if vt[from] == vector[from] + 1 do
ps
|> Enum.reject(& &1 == from)
|> check_participants(vector, vt, false)
end
end
def check_participants([], _, _, bool), do: bool
def check_participants([h | t], vector, vt, bool) do
check_participants(t, vector, vt, vt[h] > vector[h])
end
I'll briefly explain it.
First, note that I've pattern matched the inputs, to pull out the interesting parts we're using in the function body. This gets rid of some of the repetitive firstMsg.from
business. (Btw, snake_case
your variable names.)
Second, I haven't touched the gnarly outer if-condition. I simply don't know what it means. You should perhaps extract it and give it an intention revealing name.
The real action begins when we pipe participants. You were filtering inside your list comprehension. I've filtered with Enum.reject/1
instead. Then we pipe the list into a recursive function. It's going to carry the boolean through to the end, starting off with false
. It needs to check values in vt
and vector
, so they're also passed in.
The first rule of recursion is the first rule of recursion. No, wait. It's to think about how to terminate the recursion. We're working through a list of participants, so we'll stop when the list is empty. At that point, we have the boolean we're looking for, so just return it.
The recursive step is to pick off an item from the list (h
), use it to determine a new boolean (vt[h] > vector[h]
) and call the function again with the rest of the list (check_participants(t, ...)
).
Hope this helps! Have fun learning functional programming!
Upvotes: 5
Reputation: 206
So here is an idea: if you are trying to make a function return a boolean, just make it return a boolean, don't assign it to a variable. Assigning inside an if/case/cond will show a warning. Also, you are not reassigning the checking
because variables bound inside the comprehension (for
) are restricted to that scope. Your best tools in Elixir will be first pattern matching and second the pipe operator, so always try to use them.
Here is an idea to refactor that code:
defmodule Testfunc do
def keep_in_pending(firstMsg, state) do
if (firstMsg.vt[firstMsg.from] == state.vector[firstMsg.from] + 1) do
state.participants
|> Enum.filter(fn (n) -> n != firstMsg.from end)
|> Enum.reduce(fn (n, _) ->
cond do
(firstMsg.vt[n] <= state.vector[n]) -> false
(firstMsg.vt[n] > state.vector[n]) -> true
end
end)
end
end
end
Upvotes: 2