Goal
マクロで使われている、__using__について分かったことをまとめる。
Dev-Environment
OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Wait a minute
Elixirのマクロが分からない・・・orz
メタプログラミングが分からない・・・orz
メタプログラミングが分からない・・・orz
正直、elixir-langのGetting Startにあるメタプログラミングが終わった後、
どうやって手を付けていけばいいのかさっぱり分からない。
どうやって手を付けていけばいいのかさっぱり分からない。
一応、最低限(?)の使う機能については知っているが、その程度・・・
でも、その後どうやって学んでいけばいいか分からない。
でも、その後どうやって学んでいけばいいか分からない。
以前、紹介した本?初心者いきなり読めるのかね・・・
まだ友人も手に入ってないみたいだから、しばらく先だしな~
まだ友人も手に入ってないみたいだから、しばらく先だしな~
仕方ないから一番簡単で理解できそうな部分からやる。
そこから色々と広げていけるか分からないが、ある部分から始める。
そこから色々と広げていけるか分からないが、ある部分から始める。
Index
|> About __using__
|> About __MODULE__
|> Apply a little bit
|> @before_compile & __before_compile__
|> Extra
|> About __MODULE__
|> Apply a little bit
|> @before_compile & __before_compile__
|> Extra
About __using__
よくマクロを使ったソースコードで以下のような記述を見かけないだろうか?
Example:
defmodule UsingModule do
defmacro __using__(_opts) do
quote do
...
end
end
end
私が理解できる部分はここが(今の)限界らしい(笑わば笑え!)
とりあえず、この部分の理解をすることが今回やることです。
とりあえず、この部分の理解をすることが今回やることです。
これ何やってんの?って話だが・・・
このモジュールをuseした時に展開される部分とのこと。
このモジュールをuseした時に展開される部分とのこと。
以下のようなuseした時。
Example:
defmodule UseModule do
use ModuleSample
end
例えば、importやaliasなんかをここでやっていたら?
Example:
defmodule ImportModule do
def import_module do
IO.puts "ImportModule.import_module/0"
end
end
defmodule UsingModule do
defmacro __using__(_opts) do
quote do
import ImportModule
end
end
end
defmodule UseModule do
use UsingModule
def test do
import_module
end
end
Result:
iex> UseModule.test
ImportModule.import_module/0
:ok
インポートされているので、ImportModuleの関数を使えていますね。
もう少し、この部分の展開を見てみる。
defmodule UseModule do
use UsingModule
def test do
import_module
end
end
つまり、useの部分を展開するとこうなっているわけですね。
defmodule UseModule do
import ImportModule
def test do
import_module
end
end
これを少し応用して使ってみる前に・・・一つ別のことをやる。
About __MODULE__
これも割とよく見かける記述ですね。
これの値がどうなっているの確認してみよう。
Example:
defmodule ModuleSample do
def get_module, do: __MODULE__end
Result:
iex> ModuleSample.get_module
ModuleSample
モジュールそのもの(?)が値として入っているようですね。
Description:
そのままiex上で入力してみると・・・値はnilのようです。
iex> __MODULE__
nil
Apply a little bit
useの時にオプション引数を指定する。
また、オプションの内容を表示してみる。
また、オプションの内容を表示してみる。
Example:
defmodule ImportModule do
def import_module do
IO.puts "ImportModule.import_module/0"
end
end
defmodule UsingModule do
defmacro __using__(opts) do
quote do
import ImportModule
@default_value unquote(opts)
def get_default_value do
@default_value
end
end
end
end
defmodule UseModule do
use UsingModule, a: 1, b: 2, c: 3
def test do
import_module
get_default_value
end
end
Description:
get_default_value/0の関数だが、
マクロが展開された時に定義される。
マクロが展開された時に定義される。
そのため、展開先のUseModuleの関数となる。
UsingModuleで定義しているが、UsingModuleの関数でないのは注意。
UsingModuleで定義しているが、UsingModuleの関数でないのは注意。
Result:
iex> UseModule.test
ImportModule.sample/0
[a: 1, b: 2, c: 3]
先ほどの、__MODULE__を使って自分自身をimportしてみる。
Example:
defmodule UsingModule do
defmacro __using__(opts) do
quote do
import ImportModule
import unquote(__MODULE__)
@default_value unquote(opts)
def get_default_value do
IO.inspect @default_value
end
end
end
def using_module do
IO.puts "UsingModule.using_module/0"
end
end
defmodule UseModule do
use UsingModule, a: 1, b: 2, c: 3
def test do
import_module
get_default_value
using_module
end
end
Result:
iex> UseModule.test
ImportModule.import_module/0
[a: 1, b: 2, c: 3]
UsingModule.using_module/0
:ok
@before_compile & __before_compile__
あまり理解できてないので、とりあえず使い方だけ。
Example:
defmodule UsingModule do
defmacro __using__(opts) do
quote do
Module.register_attribute(__MODULE__, :default_value, accumulate: :false)
import ImportModule
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
@default_value unquote(opts)
end
end
defmacro __before_compile__(env) do
default_value = Module.get_attribute(env.module, :default_value)
quote do
def get_default_value do
IO.inspect unquote(default_value)
end
end
end
def using_module do
IO.puts "UsingModule.using_module/0"
end
end
Result:
iex> UseModule.test
ImportModule.import_module/0
[a: 1, b: 2, c: 3]
UsingModule.using_module/0
:ok
Description:
表示結果は変わっていないが、__using__で関数の定義をしていない。
__before_compile__でget_default_value/0を定義している。
そのため展開先が、UsingModuleに変わっている。
__before_compile__でget_default_value/0を定義している。
そのため展開先が、UsingModuleに変わっている。
使った機能について・・・
- register_attribute/3
属性を登録できる関数。
登録した属性: @default_value - get_attribute/2
モジュールから指定した属性を取得できる関数。 - @before_compile
モジュールがコンパイルされる前に呼び出されるフック。
本体(?)は、__before_compile__/1の部分。
注意点として、@after_compileとは異なり、
コールバック関数 / マクロは別のモジュールへ配置する必要がある。
(コールバックが呼び出される時には、まだモジュールが存在していないため)
コールバック関数 / マクロは別のモジュールへ配置する必要がある。
(コールバックが呼び出される時には、まだモジュールが存在していないため)
別にだから何?って内容で、
メタプロと言っていいかさえ怪しいプログラムだが。
メタプロと言っていいかさえ怪しいプログラムだが。
使い方だけ把握したけど、使い道がいまいち分からんな・・・
Extra
__using__を展開させてみた。
Example:
iex> require UsingModule
nil
iex> expr = quote do: UsingModule.__using__([])
{{:., [], [{:__aliases__, [alias: false], [:UsingModule]}, :__using__]}, [],
[[]]}
iex> Macro.expand_once(expr, __ENV__)
{:__block__, [],
[{{:., [],
[{:__aliases__, [alias: false, counter: 16], [:Module]},
:register_attribute]}, [],
[{:__MODULE__, [counter: 16], UsingModule}, :default_value,
[accumulate: false]]},
{:import, [context: UsingModule, counter: 16],
[{:__aliases__, [alias: false, counter: 16], [:ImportModule]}]},
{:import, [context: UsingModule, counter: 16], [UsingModule]},
{:@, [context: UsingModule, import: Kernel],
[{:before_compile, [], [UsingModule]}]},
{:@, [context: UsingModule, import: Kernel], [{:default_value, [], [[]]}]}]}
iex> Macro.to_string(Macro.expand_once(expr, __ENV__))
"(\n Module.register_attribute(__MODULE__, :default_value, accumulate: false)\n import(ImportModule)\n import(UsingModule)\n @before_compile(UsingModule)\n @default_value([])\
n)"
Speaking to oneself
私のメタプログラミングの知識は、C++のテンプレートで止まっています・・・
(当時もバリバリ使ってたわけでも使えていたわけでもない)
(当時もバリバリ使ってたわけでも使えていたわけでもない)
Qiitaでマクロの展開手順などを記事にされている方々のように、
展開して追っていくことができるなら、少しは捗る気がします。
展開して追っていくことができるなら、少しは捗る気がします。
でも、展開をさせていく方法がいまいち分からない。
分かったてはいたが、メタプログラミングの頭もできてなければ、知識も足りない。
道のりは遠い・・・
道のりは遠い・・・