Typen, bei denen sie nicht erwartet wurden

Stellen wir uns eine Modulimplementierung vor Scaffold, die eine Struktur mit vordefinierten benutzerdefinierten Feldern generiert und diese mithilfe in ein aufgerufenes Modul einfĂŒgt use Scaffold. Beim Aufruf use Scaffold, fields: foo: [custom_type()], ... möchten wir den richtigen Typ im ConsumerModul implementieren ( common_fieldim folgenden Beispiel wird er in Scaffoldoder an einer anderen Stelle von außen definiert).



@type t :: %Consumer{
  common_field: [atom()],
  foo: [custom_type()],
  ...
}


Es wĂ€re cool, wenn wir den Typ Consumer.t()fĂŒr die zukĂŒnftige Verwendung genau generieren und eine geeignete Dokumentation fĂŒr Benutzer unseres neuen Moduls erstellen könnten .



Leuchtturm in Französisch Katalonien



Ein komplizierteres Beispiel wĂŒrde so aussehen:



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


und nach der Zusammenstellung:



defmodule Consumer do
  @type t :: %Consumer{
    version: atom(),
    foo: integer(),
    bar: binary()
  }
  defstruct ~w|version foo bar|a
end


Sieht einfach aus, oder?



Naiver Ansatz



Beginnen wir mit der Analyse des AST, in dem wir uns befinden Scaffold.__using__/1.



  defmacro __using__(opts) do
    IO.inspect(opts)
  end
#⇒ [fields: [foo: {:integer, [line: 2], []},
#            bar: {:binary, [line: 2], []}]]


Ausgezeichnet. Es sieht so aus, als wÀren wir einen Schritt vom Erfolg entfernt.



  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! Typen sind etwas Besonderes, wie man im Privoz-Gebiet sagt; Wir können sie nicht einfach ĂŒberall vom AST nehmen und bekommen. Vielleicht unquotefunktioniert es vor Ort?



      @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


Egal wie es ist. Typen sind lĂ€stig; Fragen Sie jeden, der seinen Lebensunterhalt mit einem Haskell verdient (und dies sind die Rauchertypen in Haskell; realabhĂ€ngige Typen sind hundertmal nĂŒtzlicher, aber zweihundertmal schwieriger).



, , 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):



Screenshot der Typdefinition



: 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