如何为Elixir Structs验证/执行值的类型和范围?
e.g。在Struct创建期间,如果传递了无效的类型/值,则抛出错误
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
答案 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/2
,does not check anything:
struct(Location, [lat: 0, lon: 0])
#⇒ %Location{lat: 0, lon: 0}
也就是说,不应该将struct
视为Elixir类型层次结构中的一等公民。这是一个带有附加字段__struct__
的地图。
答案 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