Elixir
メタプログラミング

【初心者向け】世界一簡単な Elixir メタプロ入門

概要

プログラミングをやっているとかなりたどり着くメタプログラミング(以下、メタプロ)。コードをキレイに書きたい!とか、便利なライブラリ作りたい!とかなってくると絶対に避けられない道です。

本稿では、Elixir でメタプロをするにあたって基本的な概念とコードを紹介します。

メタプロとは

そもそもメタプロとは何でしょうか?

wikipedia によると、

メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法

ref. メタプログラミング | wikipedia

とのことです。めっちゃ噛み砕いて言うと、プログラミングでプログラミングを書く手法みたいなことです。

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 です。unquotequote の式を修正する際に利用するものです。

たとえば、以下のような挨拶をする関数を定義します。

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