Scaffold事前定義されたカスタムフィールドを持つ構造を生成し、を使用して呼び出されたモジュールにそれを挿入するモジュール実装を想像してみましょうuse Scaffold。呼び出されたときuse Scaffold, fields: foo: [custom_type()], ... に、Consumerモジュールに正しい型を実装する必要があります(common_field以下の例Scaffoldでは、外部から内部または他の場所で定義されています)。
@type t :: %Consumer{
common_field: [atom()],
foo: [custom_type()],
...
}
Consumer.t()将来使用するためにタイプを正確に生成し、新しいモジュールのユーザー向けに適切なドキュメントを作成できれば素晴らしいと思います。

より複雑な例は次のようになります。
defmodule Scaffold do
defmacro __using__(opts) do
quote do
@fields unquote(opts[:fields])
@type t :: %__MODULE__{
version: atom()
# magic
}
defstruct @fields
end
end
end
defmodule Consumer do
use Scaffold, fields: [foo: integer(), bar: binary()]
end
そして、コンパイル後:
defmodule Consumer do
@type t :: %Consumer{
version: atom(),
foo: integer(),
bar: binary()
}
defstruct ~w|version foo bar|a
end
簡単そうですね。
素朴なアプローチ
取得するASTを分析することから始めましょうScaffold.__using__/1。
defmacro __using__(opts) do
IO.inspect(opts)
end
#⇒ [fields: [foo: {:integer, [line: 2], []},
# bar: {:binary, [line: 2], []}]]
優秀な。私たちは成功から一歩離れているように見えます。
quote do
custom_types = unquote(opts[:fields])
...
end
#⇒ == Compilation error in file lib/consumer.ex ==
# ** (CompileError) lib/consumer.ex:2: undefined function integer/0
バムス!Privoz地域で言われているように、タイプは特別なものです。どこでもASTから取得して取得することはできません。多分それunquoteはローカルで動作しますか?
@type t :: %__MODULE__{
unquote_splicing([{:version, atom()} | opts[:fields]])
}
#⇒ == Compilation error in file lib/scaffold.ex ==
# ** (CompileError) lib/scaffold.ex:11: undefined function atom/0
どんなに。タイプは面倒です。Haskellで生計を立てている人に聞いてみてください(これらはまだHaskellの喫煙者のタイプです。実際の依存型のタイプは、100倍便利ですが、200倍難しいです)。
, , AST , , .
AST
, , . , , , - . , . , , AST ( unquote binary() , CompileError.
, quote do, , quote, — AST.
quote do
Enum.map([:foo, :bar], & &1)
end
#⇒ {
# {:., [], [{:__aliases__, [alias: false], [:Enum]}, :map]}, [],
# [[:foo, :bar], {:&, [], [{:&, [], [1]}]}]}
? , AST, Enum, :map, . , AST quote . .
, AST AST, . ? — , .
defmacro __using__(opts) do
fields = opts[:fields]
keys = Keyword.keys(fields)
type = ???
quote location: :keep do
@type t :: unquote(type)
defstruct unquote(keys)
end
end
, , — AST, . , ruby !
iex|1 quote do
...|1 %Foo{version: atom(), foo: binary()}
...|1 end
#⇒ {:%, [],
# [
# {:__aliases__, [alias: false], [:Foo]},
# {:%{}, [], [version: {:atom, [], []}, foo: {:binary, [], []}]}
# ]}
?
iex|2 quote do
...|2 %{__struct__: Foo, version: atom(), foo: binary()}
...|2 end
#⇒ {:%{}, [],
# [
# __struct__: {:__aliases__, [alias: false], [:Foo]},
# version: {:atom, [], []},
# foo: {:binary, [], []}
# ]}
, , . .
defmacro __using__(opts) do
fields = opts[:fields]
keys = Keyword.keys(fields)
type =
{:%{}, [],
[
{:__struct__, {:__MODULE__, [], ruby}},
{:version, {:atom, [], []}}
| fields
]}
quote location: :keep do
@type t :: unquote(type)
defstruct unquote(keys)
end
end
, Scaffold, ( : Qqwy here). , , version: atom() quote .
defmacro __using__(opts) do
fields = opts[:fields]
keys = Keyword.keys(fields)
fields_with_struct_name = [__struct__: __CALLER__.module] ++ fields
quote location: :keep do
@type t :: %{unquote_splicing(fields_with_struct)}
defstruct unquote(keys)
end
end
(mix docs):

: AST
, AST __using__/1 , ? , unquote quote? , , . , .
NB ,atom(), , ,GenServer.on_start(). .
, quote do, - atom() ( CompileError, ). , - :
keys = Keyword.keys(fields)
type =
{:%{}, [],
[
{:__struct__, {:__MODULE__, [], ruby}},
{:version, {:atom, [], []}}
| Enum.zip(keys, Stream.cycle([{:atom, [], []}]))
]}
, @type? Quoted Fragment, :
defmodule Squares do
Enum.each(1..42, fn i ->
def unquote(:"squared_#{i}")(),
do: unquote(i) * unquote(i)
end)
end
Squares.squared_5
#⇒ 25
Quoted Fragments quote, (bind_quoted:). .
defmacro __using__(opts) do
keys = Keyword.keys(opts[:fields])
quote location: :keep, bind_quoted: [keys: keys] do
type =
{:%{}, [],
[
{:__struct__, {:__MODULE__, [], ruby}},
{:version, {:atom, [], []}}
| Enum.zip(keys, Stream.cycle([{:atom, [], []}]))
]}
# ⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓
@type t :: unquote(type)
defstruct keys
end
end
unquote/1 , bind_quoted: quote/2.
!