概要
プログラミングをやっているとかなりたどり着くメタプログラミング(以下、メタプロ)。コードをキレイに書きたい!とか、便利なライブラリ作りたい!とかなってくると絶対に避けられない道です。
本稿では、Elixir でメタプロをするにあたって基本的な概念とコードを紹介します。
メタプロとは
そもそもメタプロとは何でしょうか?
wikipedia によると、
メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法
とのことです。めっちゃ噛み砕いて言うと、プログラミングでプログラミングを書く手法みたいなことです。
Elixir でメタプロ
では、さっそく Elixir でメタプロをどうやっていくのかを紹介します。
Quote
一番の基本は Quote
と呼ばれるものです。 Quote
は Elixir のプログラムをそのまま評価します。
たとえば、以下のコード。hoge という関数を定義しました。
defmodule QuoteTest do
def hoge do
IO.puts 'hoge だよ'
end
end
これと全く同じものを quote を使って書くと以下のようになります。
defmodule QuoteTest do
# defmacro の説明は後ほど行います。
defmacro hoge do
quote do
IO.puts 'hoge だよ'
end
end
end
これは何をやっているかというと、quote
のブロックで囲まれた部分をそのまま文字通りに評価しているのです。Elixir は quote
で囲まれたところを Elixir のプログラミングとして認識してくれると覚えておきましょう。
実際に動かしてみると以下のようにちゃんと動作します。
iex> require QuoteTest
iex> QuoteTest.hoge()
hoge だよ
:ok
Unquote
quote
の次に重要なのが unquote
です。unquote
は quote
の式を修正する際に利用するものです。
たとえば、以下のような挨拶をする関数を定義します。
defmodule UnquoteTest do
def hello do
IO.puts 'こんにちは'
end
end
さきほどの要領でまずは puts の部分を quote で囲いあえてメタプロしていきます。
defmodule UnquoteTest do
defmacro hello do
+ quote do
+ IO.puts 'こんにちは'
+ end
end
end
puts する内容を式で評価して変更したい場合どうしたらよいでしょうか。こういった場合に unquote を使います。
defmodule UnquoteTest do
defmacro hello do
name = 'test'
quote do
hello = "こんにちは #{unquote(name)}"
IO.puts(hello)
end
end
end
iex> UnquoteTest.hello()
こんにちは test
:ok
unquote
は引数で渡されたものを評価します。そして評価されたものを Elixir のコードとして認識するようになります。
なので上のコードは Elixir では以下のように認識されています。
defmodule UnquoteTest do
def hello do
hello = "こんにちは test"
IO.puts(hello)
end
end
逆にこの場合、unquote
を使わないとどうなるかというと、name
は変数の name としてだけ扱われます。そして quote 内で name は定義されていないので undefined function name/0
となります。以下のような状態です。
defmodule UnquoteTest do
def hello do
hello = "こんにちは #{name}"
IO.puts(hello)
end
end
unquote による関数の定義
また unquote の便利な使い方を紹介します。関数を定義することです。unquote の引数は評価されてから Elixir の式として認識されるようになるので以下のように利用することができます。
defmodule MacroTest do
Enum.each ['mike', 'alice', 'bob'], fn name ->
def unquote(:"hello_#{name}")() do
IO.puts("hello #{unquote(name)}")
end
end
end
iex> MacroTest.hello_mike
hello mike
:ok
each によってわかってきた文字列を atom に変えて、それを unquote を使って elixir のコードとして認識されているのです。
マクロ
さいごにマクロ。
In the simplest of terms macros are special functions designed to return a quoted expression that will be inserted into our application code. (マクロを簡単に表現すると、アプリケーションコードに挿入されるquoteで囲まれた式を返す関数)
ref. https://elixirschool.com/jp/lessons/advanced/metaprogramming/
もっと簡単にいうと、quote と unquote を使って作った関数ということです。
ここでは詳しく見ていきませんが、quote と unquote を理解できれば、理解するのは難しくはありません。
Elixir Schoolの マクロを参考にするのがおすすめです。
参考文献
メタプログラミング | Elixir School
How to create a dynamic function name using Elixir macro? | Stackoverflow