Enumerable not implemented for nil of type Atom

G’day OGs
I have this code as follows:

def create(conn, %{"a" => a_params})  do
    with true <- someClause(),
         a_params <- someClause(a_params),
         :ok <- Enum.reduce_while(get_in(a_params, ["b"]), %{}, fn {_key, %{"c" => c , "d" => d}}, _iteration ->
          case Module.check(c, d) do
            :ok -> {:cont, :ok}
            {:error, "..."} ->
                {:halt, {:error, "..."}}
          end
         end), do
          #do things
    else
        {:error, "..."} -> throw errors
    end
end

In some tests, %{b} in a_params would be nil
So reduce_while/3 would fail
How can I prevent reduce_while/3 from executing if my map is nil along the above condition ?.
Thank you.

not sure if I’m misunderstanding, but if you only have one key to access just use Map.get(map, key, default).
Or what do you want to happen if it’s nil?

Hi @KristerV
Thanks for your reply, I need to access multiple keys and that would return one variable only, and if it’s nil, just return :ok, let it pass, because some scenarios that are not involved in that map

I would factor it like this which I think is a bit clearer and fits your use case?

def create(conn, %{"a" => a_params})  do
  with true <- some_clause(),
    a_params <- some_other_clause(a_params),
    :ok <- check(a_params) do
      IO.puts("Do some stuff")
  end
end

def check(%{"b" => b}) do
  Enum.reduce_while(b, %{}, fn 
    {_key, %{"c" => c , "d" => d}}, _iteration ->
      check_module(c, d)

    _other, _iteration ->
      {:halt, {:error, "..."}}
  end
end

def check(other) do 
  {:error, "..."}
end

def check_module(c, d) do
  case Module.check(c, d) do
    :ok -> 
      {:cont, :ok}
    {:error, "..."} ->
      {:halt, {:error, "..."}}
  end
end

def check(other) do 
  {:error, "..."}
end

I typically find that a function over a few lines long is hard to read and harder to refactor (and this refactor definitely isn’t all it could be). Multiple function clauses (both named and anonymous) are your friends here.

you know i re-read your question and realized you can just add a filter into the with statement above the reduce.

iex(1)> a = %{"b" => 1}
%{"b" => 1}
iex(2)> with val when not is_nil(val) <- Map.get(a, "b"), do: true          
true
iex(3)> a = %{}                                                   
%{}
iex(4)> with val when not is_nil(val) <- Map.get(a, "b"), do: true
nil

Or personally I would look for a way where there’s less if statements like that. Instead the result of the reduce should always be the same datatype. In case of the map, an empty map. I think replacing get_in(a_params, ["b"]) with Map.get(a_params, "b", []) would accomplish that.