返回实现者结构类型的Elixir行为



我有一种行为来抽象各种Phoenix端点的解析URL查询参数。它看起来像这样:

defmodule Query do
@callback from_query_params(params :: %{optional(String.t()) => any()}) ::
{:ok, parsed :: struct} | {:error, reason :: atom}
end

一个简单的实现如下:

defmodule SearchQuery do
@moduledoc "Parses URL query params for search endpoint"
@behaviour Query
@enforce_keys [:search_term]
defstruct @enforce_keys
@typespec t :: %__MODULE__{search_term: String.t()}
@impl Query
def from_query_params(%{"query" => query}) when query != "" do
{:ok, %__MODULE__{search_term: query}}
end
def from_query_params(_), do: {:error, :missing_search_term}
end

我真正想说的是:

  • 实现模块应该提供一个结构(称为t()(
  • from_query_params/1上的成功类型应该使用结构t(),而不仅仅是任何结构

我怀疑Elixir类型规范语言中没有办法表达这一点,但我很高兴被证明是错误的。

虽然无法在类型规范中表达这一点,但可以通过一些元编程来部分满足需求。

如果您同意每个实现都有自己的Query行为来区分返回类型,那么可以使用

defmodule QueryBuilder do
defmacro __using__(opts \ []) do
quote do
impl = __MODULE__
defmodule Query do
@callback from_query_params(map()) :: {:ok, %unquote(impl){}}
def __after_compile__(env, _bytecode),
do: env.module.__struct__
end
@behaviour Query
@after_compile Query
end
end
end

使用use QueryBuilder代替@behaviour Query。这样,嵌套的Query模块将具有正确的返回类型,并且如果目标模块没有声明结构,编译器回调将引发。

最新更新