Elixir 在创建%MyStruct{}后实现回调

Elixir 在创建%MyStruct{}后实现回调,elixir,Elixir,在Elixir中,我希望在数据结构创建时强制验证它。为了实现这一点,我假设强制调用一个回调函数,该函数刚创建数据结构,如果该数据结构有效,则返回该数据结构,或者返回一个错误元组 实施的方法是什么 如果我声明一些行为,比如ValidatedStruct,并在结构的模块中实现回调,那么在调用代码中返回新结构之前,我如何在结构初始化时强制调用它?您可以通过一个函数创建结构,您可以将该函数命名为new,并从中调用验证方法在那里 例:(未经测试) 您可以通过一个函数创建结构,您可以将该函数命名为new,并

在Elixir中,我希望在数据结构创建时强制验证它。为了实现这一点,我假设强制调用一个回调函数,该函数刚创建数据结构,如果该数据结构有效,则返回该数据结构,或者返回一个错误元组

实施的方法是什么


如果我声明一些行为,比如ValidatedStruct,并在结构的模块中实现回调,那么在调用代码中返回新结构之前,我如何在结构初始化时强制调用它?

您可以通过一个函数创建结构,您可以将该函数命名为
new
,并从中调用验证方法在那里

例:(未经测试)


您可以通过一个函数创建结构,您可以将该函数命名为
new
,并从中调用验证方法

例:(未经测试)


您不能保证结构包含Elixir中的有效值。结构只是一个映射,其中包含一个包含原子(通常是模块名)的
\uuuuuuuuuuuuuuuuuuu结构
字段。您可以获取任何映射,添加一个
\uuuuuuuuuuuuuuuuuuu
字段,然后它就变成了该结构

例如,我在这里构建了一个
MapSet
struct,没有任何其他字段
iex
甚至无法打印结构,因为
Inspect
MapSet
实现假定结构中有一个
map
键包含映射:

iex(1)> %{__struct__: MapSet}
%Inspect.Error{message: "got FunctionClauseError with message \"no function clause matching in MapSet.to_list/1\" while inspecting \e[39m%{\e[0m\e[33m\e[36m__struct__: \e[0m\e[33m\e[36mMapSet\e[0m\e[33m\e[39m}\e[0m\e[33m"}
Elixir库通常做的是在模块中添加一个
新的
函数,该函数接受参数并在有效输入时返回
{:ok,struct}
,在失败时返回
{:error,“description”}
(或者只返回
:error
)。这不会阻止用户使用
%ModuleName{}
语法创建结构。您可以通过添加一个默认为
false
valid?
字段来添加一个简单的防范措施,然后在所有函数中检查该值是否为true
exto.Changeset
使用类似的技术,使得
Repo.insert
exto.Changeset
中出现错误时,甚至不尝试在数据库中插入数据。同样,这是一个很容易忽略的问题

下面是一个例子:

defmodule MyStruct do
  defstruct [:x, :y, valid?: false]

  # We want `x` and `y` to always be integers.
  def new(x, y) when is_integer(x) and is_integer(y) do
    {:ok, %__MODULE__{x: x, y: y, valid?: true}}
  end
  def new(_, _), do: :error

  def print(%__MODULE__{x: x, y: y, valid?: true}) do
    IO.inspect {x, y}
  end
end

defmodule Main do
  def main do
    IO.inspect MyStruct.new(1, 2)
    IO.inspect MyStruct.new(1, 2.3)
    {:ok, a} = MyStruct.new(1, 2)
    MyStruct.print(a)
    try do
      # This will throw an error because `valid?` will be false.
      MyStruct.print(%MyStruct{})
    rescue
      e -> IO.inspect e
    end
    # This however will work and there's no way to stop it.
    MyStruct.print(%MyStruct{x: "x", y: "y", valid?: true})
  end
end

Main.main
输出:

{:ok, %MyStruct{valid?: true, x: 1, y: 2}}
:error
{1, 2}
%FunctionClauseError{arity: 1, function: :print, module: MyStruct}
{"x", "y"}

您不能保证结构包含Elixir中的有效值。结构只是一个映射,其中包含一个包含原子(通常是模块名)的
\uuuuuuuuuuuuuuuuuuu结构
字段。您可以获取任何映射,添加一个
\uuuuuuuuuuuuuuuuuuu
字段,然后它就变成了该结构

例如,我在这里构建了一个
MapSet
struct,没有任何其他字段
iex
甚至无法打印结构,因为
Inspect
MapSet
实现假定结构中有一个
map
键包含映射:

iex(1)> %{__struct__: MapSet}
%Inspect.Error{message: "got FunctionClauseError with message \"no function clause matching in MapSet.to_list/1\" while inspecting \e[39m%{\e[0m\e[33m\e[36m__struct__: \e[0m\e[33m\e[36mMapSet\e[0m\e[33m\e[39m}\e[0m\e[33m"}
Elixir库通常做的是在模块中添加一个
新的
函数,该函数接受参数并在有效输入时返回
{:ok,struct}
,在失败时返回
{:error,“description”}
(或者只返回
:error
)。这不会阻止用户使用
%ModuleName{}
语法创建结构。您可以通过添加一个默认为
false
valid?
字段来添加一个简单的防范措施,然后在所有函数中检查该值是否为true
exto.Changeset
使用类似的技术,使得
Repo.insert
exto.Changeset
中出现错误时,甚至不尝试在数据库中插入数据。同样,这是一个很容易忽略的问题

下面是一个例子:

defmodule MyStruct do
  defstruct [:x, :y, valid?: false]

  # We want `x` and `y` to always be integers.
  def new(x, y) when is_integer(x) and is_integer(y) do
    {:ok, %__MODULE__{x: x, y: y, valid?: true}}
  end
  def new(_, _), do: :error

  def print(%__MODULE__{x: x, y: y, valid?: true}) do
    IO.inspect {x, y}
  end
end

defmodule Main do
  def main do
    IO.inspect MyStruct.new(1, 2)
    IO.inspect MyStruct.new(1, 2.3)
    {:ok, a} = MyStruct.new(1, 2)
    MyStruct.print(a)
    try do
      # This will throw an error because `valid?` will be false.
      MyStruct.print(%MyStruct{})
    rescue
      e -> IO.inspect e
    end
    # This however will work and there's no way to stop it.
    MyStruct.print(%MyStruct{x: "x", y: "y", valid?: true})
  end
end

Main.main
输出:

{:ok, %MyStruct{valid?: true, x: 1, y: 2}}
:error
{1, 2}
%FunctionClauseError{arity: 1, function: :print, module: MyStruct}
{"x", "y"}

当用户使用
%SomeModule{foo:bar}
语法创建一个结构时,您希望自动调用此回调?@Dogbert是的,没错。不过,如果客户端代码需要检查自身,它可能足以使这样的回调用于客户端代码。然后在进一步处理过程中调用回调。我仍然不知道在长生不老药中做这样一个完整性检查的最佳方法是什么。来自Scala后台。当用户使用
%SomeModule{foo:bar}
语法创建结构时,您希望自动调用此回调?@Dogbert是的,没错。不过,如果客户端代码需要检查自身,可能只需使此回调可用于客户端代码。然后在进一步处理过程中调用回调。我仍然不知道在长生不老药中做这样一个完整性检查的最佳方法是什么。来自Scala背景。旁注:开始一个管道时,一定要使用一个术语:
\uuuuuuu模块\uuuuuu124;>\ uuuu结构(opts)|>验证
。例如,感谢@nicolas garnil,我将测试它并用我的观察结果编写另一个注释。在函数
新建
中,您应该调用
结构(Kernel.struct/2),而不是
\uuu结构
。我不想在结构本身中存储错误。通常,它可以仅包装一个值。比如%OrderId{v:“some_order_id”}。错误报告应与结构分离。我觉得它更像一元。然后,它可以在
with
块中使用。侧注:
with
块不需要一个明确的术语来保留错误,通过模式匹配可以很容易地实现类似于一元的行为:
with%MyStruct{errors:nil}errors(errors)end
。侧注:始终以一个术语开始管道:
\uuuuuu MODULE\u124;>\ uu结构__(选择)|>验证
。例如,感谢@nicolas garnil,我将测试它,并用我的观察结果编写另一条注释。在函数
new
中,您应该调用
struct
(Kernel.struct/2),而不是
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
。我不想在结构本身中存储错误。错误报告应该与结构分离。我认为它更像一元结构。然后它可以在
with
块中使用。旁注:
with
块不需要明确的术语来保留错误,可以使用类似一元结构的行为