Elixir 中的或行为

OR behaviour in Elixir

我有以下情况,一张带有 false 布尔值 属性 的地图。当我尝试将具有 or 条件的值分配给另一张地图时,我收到了 nil 而不是我预期的 false

iex(1)> my_map = %{"email_sended" => false}                          
%{"a" => false}

iex(2)> other_map = %{ email_sended: my_map["email_sended"] || my_map[:email_sended] }
%{email_sended: nil}

iex(3)> other_map[:email_sended]
nil

我的问题是,有一种简洁的方法可以做到这一点,而不必像这样设置 if 条件吗?

iex(4)> if Map.has_key?(my_map, "email_sended"), do: my_map["email_sended"], else: my_map[:email_sended]
false

如果您查看 ||, && and ! 运算符文档,您会发现:

all values except false and nil will evaluate to true:

iex> false || 11
11

这意味着 || 运算符不能可靠地用于此类事情。

总的来说你的方法是错误的,你应该知道你使用的是什么类型的键,因为有 advantages/disadvantages 到使用 atoms/strings 键。

如果您仍想完成这项工作,最好的方法是制作一个辅助函数:

def get_key(map, key) when is_map(map) and is_binary(key) do
  case map[key] do
    nil -> map[String.to_atom(key)]
    value -> value
  end
end

然而,这仍然不完美,因为 atom 键也可能不存在,你将得到 nil。

将任何值转换为布尔值的一个(有时被认为是肮脏的)技巧是 double not:

iex> !!(nil || true)
true

iex> !!(nil || false)
false

但请注意,除 nilfalse 以外的任何值都被认为是真实的,所以不要落入这个陷阱:

iex> !!(true || "on") # seems to work
true

iex> !!(false || "off") # oops
true

但在您开始使用它之前,最好消除应用程序中关于对同一信息使用字符串键和原子键的不一致性。

以我的愚见,最可靠的方法是使用 Map.get/3 明确检查密钥是否存在。

other_map = %{email_sended:
  Map.get(my_map, "email_sended", my_map[:email_sended]) 
}
#⇒ %{email_sended: false}

甚至可能会做两次。

value = 
  Map.get(my_map, "email_sended",
    Map.get(my_map, :email_sended, {:error, :not_found}))
other_map = %{email_sended: value}
#⇒ %{email_sended: false}

如果您只是在寻找一个简短的内联解决方案,只需将其与 false 进行或运算即可处理 nil 情况:

iex> my_map = %{"email_sended" => false}
%{"email_sended" => false}
iex> my_map["email_sended"] || my_map[:email_sended] || false
false

如果您正在寻找更强大的解决方案,casewith 是不错的选择:

iex> case my_map do
...>   %{"email_sended" => sent} -> sent
...>   %{email_sended: sent} -> sent
...>   %{} -> false
...> end
false
iex> with :error <- Map.fetch(my_map, "email_sended"),
...>      :error <- Map.fetch(my_map, :email_sended) do
...>   false
...> else
...>   {:ok, sent} ->
...>     sent
...> end
false