如何为Elixir结构验证/强制执行类型和值?

时间:2018-06-17 16:26:05

标签: elixir

如何为Elixir Structs验证/执行值的类型和范围?

e.g。在Struct创建期间,如果传递了无效的类型/值,则抛出错误

  • lat应为数字,介于-90.0和+90.0之间
  • lon应为数字,介于-180.0和+180.0
  • 之间

defmodule Location do
  @enforce_keys [:lat, :lon]
  defstruct lat: 0, lon: 0
end

此处与@JoséValim进行了一些讨论,但不清楚结果是什么 https://groups.google.com/forum/#!topic/elixir-lang-core/U_wdxEqWj_Y

3 个答案:

答案 0 :(得分:4)

无论您是在寻找终身保护/类型保证,都不可能。结构是bare maps underneath

defmodule Location do
  @enforce_keys [:lat, :lon]
  defstruct lat: 0, lon: 0
end

loc = %Location{lat: 0, lon: 0}
is_map(loc) #⇒ true

甚至更多,人们可能只是创建一个map,其中__struct__键设置为原子,表示结构名称,并且vo:

loc_str = %{__struct__: Location, lat: 0, lon: 0}
#⇒ %Location{lat: 0, lon: 0}

或使用Kernel.struct/2does not check anything

struct(Location, [lat: 0, lon: 0])
#⇒ %Location{lat: 0, lon: 0}

也就是说,不应该将struct视为Elixir类型层次结构中的一等公民。这是一个带有附加字段__struct__的地图。

在Elixir中,我们通常使用Typespecsdialyzer进行静态代码分析。

答案 1 :(得分:3)

就像@mudasobwa所说,由于Elixir是一种动态类型化的语言,因此您无法在Elixir的每一步都做出这些保证。但是您可以在辅助函数中构建结构时执行此操作。

下面是one of my projects的示例:

defmodule Location do
  defstruct [:latitude, :longitude]

  @moduledoc "A struct representation of geo-coordinates"

  @latitude  %{max: +90,  min: -90}
  @longitude %{max: +180, min: -180}


  @doc "Return a new struct for given lat/longs"
  def new(long, lat) do
    validate_latitude!(lat)
    validate_longitude!(long)

    %Location{latitude: lat, longitude: long}
  end


  # Raise error if latitude is invalid
  defp validate_latitude!(lat) do
    case is_number(lat) && (lat <= @latitude.max) && (lat >= @latitude.min) do
      true -> :ok
      false ->
        raise Location.InvalidData, message: "Invalid value for Latitude"
    end
  end


  # Raise error if longitude is invalid
  defp validate_longitude!(long) do
    case is_number(long) && (long <= @longitude.max) && (long >= @longitude.min) do
      true -> :ok
      false ->
        raise Location.InvalidData, message: "Invalid value for Longitude"
    end
  end

end

答案 2 :(得分:0)

Python人用“ pythonic”一词来描述惯用的Python代码。我希望我们对Elixir也一样(“ elixirish”?)。考虑到这一点,我试图使@Sheharyar的解决方案更加冗长一些,并且更具功能性,并省略了所有条件构造:

defmodule Location do
  @moduledoc "A struct representation of geo-coordinates."

  defstruct [:longitude, :latitude]

  @doc "Return a new struct for given lat/longs"
  def new(long, lat) do
    valid_latitude?(lat)
    valid_longitude?(long)

    %Location{longitude: long, latitude: lat}
  end

  # Raise error if latitude is invalid
  defp valid_latitude?(lat) 
    when is_number(lat) and (-90 <= lat) and (lat <= +90),
    do: :ok

  defp valid_latitude?(_lat),
    do: raise "Invalid value for latitude; valid: -90..+90."

  # Raise error if longitude is invalid
  defp valid_longitude?(long)
    when is_number(long) and (-180 <= long) and (long <= +180),
    do: :ok

  defp valid_longitude?(_long),
    do: raise "Invalid value for longitude; valid: -180..+180."
end

当然,几乎总是有改进的余地:

defmodule Location do
  @moduledoc "A struct representation of geo-coordinates."

  defstruct [:longitude, :latitude]

  @doc "Return a new struct for given lat/longs"
  def new(long, lat) when
    is_number(long) and (long in -180..+180) and
    is_number(lat) and (lat in -90..+90),
  do: %Location{longitude: long, latitude: lat}

 def new(_, _),
   do: raise "Valid latitudes: -90..+90; valid longitudes: -180..+180."
end