Jenis yang tidak diharapkan

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.



Mercusuar di Katalonia Prancis



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



Tangkapan layar definisi tipe



: 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