الأنواع التي لم تكن متوقعة

دعنا نتخيل تطبيقًا للوحدة النمطية 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


لا يهم كيف هو. أنواع ممل؛ اسأل أي شخص يكسب رزقه مع هاسكل (وهذه هي أنواع المدخن في هاسكل ؛ أنواع المدخن الحقيقي - المعتمد - هي أكثر فائدة مائة مرة ، لكنها أكثر صعوبة بمئتي مرة).



, , 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.






!




All Articles