Types oĂč ils n'Ă©taient pas attendus

Imaginons une implĂ©mentation de module Scaffoldqui gĂ©nĂšre une structure avec des champs personnalisĂ©s prĂ©dĂ©finis et l'injecte dans un module appelĂ© en utilisant use Scaffold. Lorsqu'il est appelĂ© use Scaffold, fields: foo: [custom_type()], ... , nous voulons implĂ©menter le type correct dans le Consumermodule ( common_fielddans l'exemple ci-dessous, il est dĂ©fini dans Scaffoldou ailleurs de l'extĂ©rieur).



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


Ce serait cool si nous pouvions générer avec précision le type Consumer.t()pour une utilisation future et créer une documentation appropriée pour les utilisateurs de notre nouveau module.



Phare en Catalogne française



Un exemple plus compliqué ressemblerait à ceci:



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


et, aprĂšs compilation:



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


Ça a l'air facile, non?



Approche naĂŻve



Commençons par analyser dans quel AST nous entrons Scaffold.__using__/1.



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


Excellent. Il semble que nous soyons Ă  un pas du succĂšs.



  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! Les types sont quelque chose de spĂ©cial, comme on dit dans la rĂ©gion de Privoz; nous ne pouvons pas simplement les prendre et les obtenir de l'AST partout. Peut-ĂȘtre que cela unquotefonctionnera localement?



      @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


Peu importe comment c'est. Les types sont fatigants; demandez à quiconque gagne sa vie avec un Haskell (et ce sont les types de fumeurs à Haskell; les vrais - dépendants - sont cent fois plus utiles, mais deux cents fois plus difficiles).



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



Capture d'écran de la définition de type



: 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