Mari kita bayangkan implementasi modul Scaffoldyang menghasilkan struktur dengan bidang khusus yang telah ditentukan sebelumnya dan memasukkannya ke dalam modul yang disebut menggunakan use Scaffold. Saat dipanggil use Scaffold, fields: foo: [custom_type()], ... , kami ingin mengimplementasikan tipe yang benar dalam Consumermodul ( common_fielddalam contoh di bawah ini, didefinisikan di dalam Scaffoldatau di tempat lain dari luar).
@type t :: %Consumer{
common_field: [atom()],
foo: [custom_type()],
...
}
Alangkah baiknya jika kita dapat secara akurat menghasilkan jenis Consumer.t()untuk penggunaan di masa mendatang dan membuat dokumentasi yang sesuai untuk pengguna modul baru kita.

Contoh yang lebih rumit akan terlihat seperti ini:
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
dan, setelah kompilasi:
defmodule Consumer do
@type t :: %Consumer{
version: atom(),
foo: integer(),
bar: binary()
}
defstruct ~w|version foo bar|a
end
Terlihat mudah, bukan?
Pendekatan naif
Mari kita mulai dengan menganalisis AST apa yang kita dapatkan Scaffold.__using__/1.
defmacro __using__(opts) do
IO.inspect(opts)
end
#โ [fields: [foo: {:integer, [line: 2], []},
# bar: {:binary, [line: 2], []}]]
Luar biasa. Sepertinya kita selangkah lagi dari kesuksesan.
quote do
custom_types = unquote(opts[:fields])
...
end
#โ == Compilation error in file lib/consumer.ex ==
# ** (CompileError) lib/consumer.ex:2: undefined function integer/0
Bams! Jenis adalah sesuatu yang istimewa, seperti yang mereka katakan di area Privoz; kita tidak bisa begitu saja mengambil dan mendapatkannya dari AST di mana pun. Mungkin itu unquoteakan bekerja secara lokal?
@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
Tidak peduli bagaimana itu. Jenis melelahkan; tanya siapa pun yang mencari nafkah dengan Haskell (dan ini masih merupakan tipe perokok di Haskell; tipe yang bergantung pada nyata - seratus kali lebih berguna, tetapi dua ratus kali lebih sulit).
, , 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.
!