如何为Elixir结构验证/强制使用值的类型和范围?
例如,在结构创建期间,如果输入了无效的类型/值,则会引发错误
defmodule Location do
@enforce_keys [:lat, :lon]
defstruct lat: 0, lon: 0
end
Run Code Online (Sandbox Code Playgroud)
这里与@JoséValim进行了一些讨论,但不清楚结果是什么 https://groups.google.com/forum/#!topic/elixir-lang-core/U_wdxEqWj_Y
无论你是在寻找终身守卫/类型保证,这是不可能的。结构体是下面的裸图:
defmodule Location do
@enforce_keys [:lat, :lon]
defstruct lat: 0, lon: 0
end
loc = %Location{lat: 0, lon: 0}
is_map(loc) #? true
Run Code Online (Sandbox Code Playgroud)
甚至更多,一个可以简单地创建一个map与__struct__按键的原子,表示该结构的名称,瞧?:
loc_str = %{__struct__: Location, lat: 0, lon: 0}
#? %Location{lat: 0, lon: 0}
Run Code Online (Sandbox Code Playgroud)
struct(Location, [lat: 0, lon: 0])
#? %Location{lat: 0, lon: 0}
Run Code Online (Sandbox Code Playgroud)
也就是说,不应将structElixir 类型层次结构中的一等公民视为一等公民。这是一个带有附加字段__struct__集的地图。
在 Elixir 中,我们通常使用Typespecs并dialyzer为此目的进行静态代码分析。
就像 @mudasobwa 所说,你不能在 Elixir 中的每一步都做出这些保证,因为它是一种动态类型语言。但是您可以在辅助函数中构建结构时执行此操作。
这是我的一个项目的示例:
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
Run Code Online (Sandbox Code Playgroud)