侵略!イカ娘!がErlangにもやってきました

ついに!非常事態です!!
まずは何が起きているのか確かめましょう!

-module(spam).
-compile(export_all).
-include("geso.hrl").

ham()->    egg, hage.
hoge()->   huga.
hello()->  io:format("hello!~n", []).
eecho(E)->  io:format("~p~n", [E]).

実際にこれを対話的インターフェースで実行してみます。

shuna:~ kuenishi$ erl
Erlang R14A (erts-5.8) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8  (abort with ^G)
1> c(spam).
{ok,spam}
2> spam:ham().
ika
3> spam:hoge().
ika
4> spam:hello().
hello!
ika
5> spam:eecho("アウチ!").
[12450,12454,12481,33,10]
geso
6> 

な、なんじゃこりゃあー!! どうやら、どんな関数を実行しても戻り値がゲソかイカになってしまうらしいぞ!ついにErlangコンパイラだか仮想マシンがイカ娘の侵略を受けて、語尾がゲソだかイカになってしまったらしい!コレは困ったぞぉ!
むむ?よくみたら、

-include("geso.hrl").

なんだ、こいつが悪さしてるんじゃなイカ? もっと調べてみるでゲソ!!

侵略!イカ娘 8 (少年チャンピオン・コミックス)

侵略!イカ娘 8 (少年チャンピオン・コミックス)

geso.hrlをみてみると、…

-compile({parse_transform, geso}).

(´・ω`・)エッ?
これだけ?! gmmmmこれでは捜査が行き詰ってしまったでゲソ。よくよく同じディレクトリをみてみると

shuna:tmp kuenishi$ ls
geso.beam		icssuis501		spam.beam
geso.erl		launch-AI8kJc		spam.erl
geso.erl~		launch-T6n1Mu		spam.erl~
geso.hrl		launch-tzVD6j		ssh-Cf6yRjSivV
geso.hrl~		launchd-190.d45bEM

なんか分からないけど、geso.beamというのとgeso.erlというのがある。こいつがイカ娘の本体だな!!見つけたぞーきっつくお仕置きしてやる!

-module(geso).
-compile(export_all).

parse_transform(Forms, _Options)->
    lists:map(fun rewrite/1, Forms).
    
rewrite({function,N,Name,M, [{clause, A, L1, L2, L3}]}) when M rem 2 == 0 ->
    {function,N,Name,M, [{clause,A,L1,L2,L3++[{atom,1025,ika}]}]};
rewrite({function,N,Name,M, [{clause, A, L1, L2, L3}]}) ->
    {function,N,Name,M, [{clause,A,L1,L2,L3++[{atom,1025,geso}]}]};
rewrite(F)->    F.

アレっ?!なんだなんだわかんねーぞこれ。。。 うーんぅ…compileディレクティブだから、適当にググってみるか。そりゃあmanpageがひっかかるわな。通り一遍のことしか書いてないでしょー。うんうん。-compile(export_all).とかそういうのだよねJK
…お?

{parse_transform,Module}
Causes the parse transformation function Module:parse_transform/2 to be applied to the parsed code before the code is checked for errors.

はい。マニュアルを適当に読み飛ばしてはいけませんね。この子は何をするかというと、(たぶん)統語解析をして、Semantics checkをする前にいろいろ悪さできるかもしれないというわけです。eunitでの実際の用例をみてみましょう。ハイ、キモいですね何やってるか分からないですよね。eunitと聞いてピンときたあなた、Erlangでメシ食っていけるはずです。@voluntasに連絡だ!
eunitを使っていれば誰もが一度は不思議に思うこと、それは

  • hogehoge_test/0という関数を定義したら、それだけでeunitがテスト関数のひとつにしてくれる
  • hugahuga_test_/0という関数を定義したら、それだけでeunitがテストスイートのひとつにしてくれる

ということです。これErlangだけでやろうとしたら、code:all_loaded/0とかModule:module_info/0,1とかを使ってどういう関数が定義されているかをeunit:test/0の中でぐーんとサーチしなきゃいかんことになるわけです。実行のときにそれをやるのではちょっと遅そう。
そういうわけで、コンパイルしているときに、parse_transformでErlang VMのマシン語をちょちょーいといじってやるわけです。
詳しくは書きませんが、イカ娘はそれを利用して、中間的に生成されたVMのマシン語の最後にgesoとかikaとかいうatomをネジこんだわけですね。恐るべしイカ娘。とはいえincludeを書かないと侵略できないというのは、侵略者としては間抜けですなー。*1

Eunitは実際どうやっているかというと、eunit_autoexport.erlの中で*_testという名前を持つ関数と*_test_という名前を持つ関数があればそれを一覧に突っ込んでexportして、最後にModule:test/0という関数を作ってやるわけです。このModule:test/0の中身もすんごーく作っている…かと思いきやeunit:test/1を呼んでいるだけかというwww eunit:test/1の中身はちゃんとは見てませんが、どうせModule:module_info(exports)の結果から*_testとかにマッチしているやつを掻き集めて走らせているとか、そんなところじゃなイカ。

そういえばeunit.erlをみたらeunit:watchとかそういうのがあったりして、すわOMakeばりのファイル監視してくれるのか?!と期待してちょっといじってみたがどうやらそうではないらしい。ちゃんと動かせなかった。人柱の報告を待つことにしよう。

*1:やっぱりimport phpくらいのインパクトがないとちょっとダメかなぁ