如何检查Map是否也是一个Struct?

时间:2016-09-28 21:16:08

标签: elixir

在Elixir中,我可以通过调用Kernel.is_map/1来检查变量是map还是struct,这有意义,因为Structs are Maps underneath,但我想区分两者之间。我知道我可以在Struct上调用__struct__来获取它的模块名称但是在普通地图上调用它会抛出:

** (KeyError) key :__struct__ not found in: %{}

所以我的问题是,如何检查变量是地图还是结构?

示例用例:

# I want to handle struct and map inputs differently in my Module

defmodule DifferentThings do
  def do_something(arg) when is_map(arg) do
    # Do something with Maps
  end

  def do_something(arg) when is_struct(arg) do
    # But handle Structs differently
    # Issue is, `is_struct` does not exist
  end
end

6 个答案:

答案 0 :(得分:20)

一般来说,检查地图是否为结构:

Map.has_key?(struct, :__struct__)

对于不同的方法声明(更一般的方法是第二个):

defmodule DifferentThings do
  def do_something(%{__struct__: _} = arg) do
    # ...
  end

  def do_something(arg) when is_map(arg) do
    # ...
  end
end

答案 1 :(得分:7)

可以在结构和地图之间进行模式匹配,如下所示

defmodule DifferentThings do
  def do_something(arg = %_x{}) do
    IO.puts "This is a struct"
  end

  def do_something(arg = %{}) do
    IO.puts "This is a map"
  end
end

答案 2 :(得分:4)

对于具有防护的Map vs Struct,您不能使用单独的函数头,但您可以使用模式匹配来实现。

defmodule Guard do

  def foo(%{:__struct__ => x })  do
    Struct
  end

  def foo(x) when is_map x do
    Map
  end

end

答案 3 :(得分:3)

您可以使用keys轻松查看当前地图的Map.keys/1

对于map和struct is_map/1都将返回true,但在您的示例中:

Map.keys(%{}) will return []

Map.keys(struct) 

将返回键的集合,例如。 [:__struct__, :name, :age]

所以你可以简单地使用:

:__struct__ in Map.keys(struct).

如果您希望将此is_struct设为宏,那么。

答案 4 :(得分:1)

检查术语是否为map的最简单方法是使用:

Map.has_key?(map, :__struct__)

它不能起到保护作用,因此我们以前不得不求助于function子句中的:__struct__键以进行模式匹配:

def some_fun(%__struct__: _module}), do: ...

但这看起来很脏。


后卫:is_struct/1

从Erlang / OTP 21开始,引入了一些新的防护措施,我们可以使用它们定义自己的防护措施:

defguard is_struct(term) when is_map(term) and :erlang.is_map_key(:__struct__, term)
defguard is_struct(term, module) when is_struct(term) and :erlang.map_get(:__struct__, term) == module 

现在您可以将它们用作功能守卫:

def do_something(account) when is_struct(account, User) do
  # Do something with %User{} structs
end

def do_something(struct) when is_struct(struct) do
  # Do something with random structs
end

def do_something(map) when is_map(map) do
  # Do something with maps
end

注意:这些将很快添加到Elixir标准库本身中。请遵循此ElixirForum thread中的讨论。

答案 5 :(得分:1)

正如Sheharyar所述,Elixir 1.10.0引入了is_struct/1保护。因此,您的原始代码将可用。