#はじめに
私はISLispとPrologを主に使っています。この度、Elixirにぞっこん惚れこみました。習得のために実行時間を測定するマクロを作りました。
#マクロが必要な場合
他の投稿記事によるとErlangのライブラリのtimer.tc というものを使えば関数の実行時間を計測できることがわかりました。ところでLispやPrologでは通常、timeという述語あるいは関数が用意されていて例えば、 (time (tarai 12 6 0)) のようにして使います。Elixirでもそのようなものがあると便利だと思いました。引数は評価されてtimeに渡されてしまっては時間計測ができません。評価せずに渡すにはマクロを使う必要がありました。
#コード
まずはコードをご覧ください。私が初めてElixirで書いたコードです。
defmodule Test do
def tarai(x,y,z) do
if x <= y do
y
else
tarai(tarai(x-1,y,z),
tarai(y-1,z,x),
tarai(z-1,x,y))
end
end
defmacro time(exp) do
quote do
{time, dict} = :timer.tc(fn() -> unquote(exp) end)
IO.inspect "time: #{time} micro second"
IO.inspect "-------------"
dict
end
end
end
tarai/3は有名な竹内関数です。膨大な再帰計算を要することからベンチマークテストによく使われるものです。tarai(12,6,0) は現在の高速なコンピューターなら1秒以内には計算が終了します。(30年前のパソコン上のLispでは1日かかっても計算は終わりそうもありませんでした。)
time/1が今回、勉強しながら作った時間計測マクロです。
Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> require(Test)
Test
iex(2)> Test.time(Test.tarai(12,6,0))
"time: 109000 micro second"
"-------------"
12
iex(3)>
対話環境のIexで上記のような結果を得ました。109ミリ秒でありLisp系の処理系に比較しても高速です。
#マクロの仕組みの勉強
Lispでのマクロの仕組みを思い出しながらElixirでのマクロについて試行錯誤をしました。defmacroにより記述されるマクロは抽象構文木を受け取ります。LispではそれはS式です。Elixirはtupleというものを使うことがわかりました。quoteという仕組みによりマクロは受け取った式を構文木に変換します。そして展開後の構文を構成した後にunquoteという仕組みにより元の評価可能な式に戻しています。
quoteを使っていろいろと試してみました。
iex(3)> quote do 1+2 end
{:+, [context: Elixir, import: Kernel], [1, 2]}
iex(4)>
どうやら式は {関数子、システム用のリスト、引数のリスト}の3つの要素から構成されているらしいことがわかりました。Erlangのライブラリでは時間計測には次の使い方をするようです。
1.timer.tc(f)
2.timer.tc(:function-name,argument-list)
3.timer.tc(module-name,:function-name,argument-list)
そこで3番目の方法に従い、受け取った式を構文木にしモジュール名、関数名、引数を取り出し、再構成することを考えました。しかし、まだElixirは始めたばかりで構文木からの取り出し方、再構成の仕方がよくわかりません。さてどうしたものか?
参考にした記事では無名関数を利用していました。1の方法により無名関数にしてtimer.tcに渡す方法をとりました。マクロ展開後は次のコードになるはずです。
{time, dict} = :timer.tc(fn() -> tarai(12,6,0) end)
IO.inspect "time: #{time} micro second"
IO.inspect "-------------"
dict
Iexにコードを渡すとうまくコンパイルできたようです。実行してみるとrequireが必要だというコンパイラの指示がありました。まだ理屈はよくわかっていないのですが、とりあえずrequire(Test)とモジュール名をrequireをしたら動作しました。
#自己紹介
遅くなりましたが、自己紹介です。16bitのPC-9801の時代にPrologとLispに触れて以来、それらを楽しんでいます。自分でも言語処理系を作ってみようと思い、PrologはO-Prologというインタプリタ・コンパイラを、LispはISLisp規格のEISLというインタプリタ・コンパイラを作りました。
近年のLispはマクロが重要視されており、ひと昔前にLispを独学した私には違和感がありました。マクロは時に必要なものの、これを使いまくるとわかりにくい、バグの多いコードになると考えています。ポールグレアム氏以後、Lispにおいてテクニカルなマクロが多く使われるようになり、違和感は極めて大きくなりました。このため、Lispを離れてPrologに専念していたところが、Elixirに出会いました。これこそ私が探していた理想のLispだ!、と思いました。Erlangの影響によりProlog的なパターンマッチングがあります。Ruby風の親しみやすい構文、Standard MLの影響も見受けられました。それでいて極めてシンプルかつわかりやすい関数型言語であることがわかってきました。Lispが多くの人に使われ、親しまれてほしいと願う私にとってElixirはぴったりでした。
#終わりに
Elixirの設計をした人はどんな人? ググってみるとブラジル人でポーランド在住の若者だそうです。素晴らしい言語設計の才能を尊敬してやみません。
José Valimさん、あんた、天才や!
Elixirに惚れ込みました。Elixirコミニュティーに微力ながら尽力したいと思います。どうぞ、よろしくお願いします。
参考記事
「Elixirで関数の実行速度を測定する」
https://qiita.com/hisaway/items/2f075f1705b35e829c5c
「O-Prolog」
https://qiita.com/sym_num/items/d767d925d42e19dee2ed
「Easy-ISLisp(EISL)」
https://qiita.com/sym_num/items/0e321959afc218772e33