问题
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