How can types and values be validated / enforced for Elixir structs?

烂漫一生 提交于 2019-12-10 15:06:34

问题


How can types and ranges of values be validated / enforced for Elixir Structs?

e.g. During Struct creation, throwing an error if invalid types/values are handed in

  • lat should be numerical and between -90.0 and +90.0
  • lon should be numerical and between -180.0 and +180.0

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

There was some discussion here with @JoséValim, but not clear what the outcome was https://groups.google.com/forum/#!topic/elixir-lang-core/U_wdxEqWj_Y


回答1:


Whether you are looking for a lifetime guarding / type guarantees, it is impossible. Structs are 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

even more, one might simply create a map with __struct__ key set to the atom, denoting the struct name, and voilà:

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

or use Kernel.struct/2, that does not check anything:

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

That said, one should not treat struct as a first-class citizen in Elixir types hierarchy. It’s a map with an additional field __struct__ set.

In Elixir we commonly use Typespecs and dialyzer for static code analysis for that purpose.




回答2:


Like @mudasobwa said, you can't make these guarantees at every step in Elixir since it's a dynamically typed language. But you can do it when building a struct in a helper function.

Here's an example from 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


来源:https://stackoverflow.com/questions/50898468/how-can-types-and-values-be-validated-enforced-for-elixir-structs

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!