简体   繁体   中英

How to update Boolean variable inside function in elixir

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"].

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!

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM