うーん、そうなのだけど....

メタプログラミングとは - Perl入門〜サンプルコードによるPerl入門〜
メタプログラミングとはソースコードを生成するプログラミングのことです。メタプログラミングによって生成したソースコードは、eval関数で実行することができます。

evalだけがメタプログラミングの技法ではないし、またevalはその威力ゆえ最後の選択肢とすべきだ。

#!/usr/local/bin/perl
use strict;
use warnings;
use Benchmark qw/timethese cmpthese/;

cmpthese(
    timethese(
        0,
        {
            eval => sub {
                no warnings 'redefine'; # comment this out leter
                my $src = 'sub add{ $_[0] + $_[1] }';
                eval $src;
                add( 1, 1 ) == 2 or die;
            },
            subref => sub {
                my $add = sub { $_[0] + $_[1] };
                $add->( 1, 1 ) == 2 or die;
              }
        }
    )
);

これを手元で実行してみると、以下のような結果となる。

            Rate   eval subref
eval     33283/s     --   -97%
subref 1021681/s  2970%     --

こんな単純な例でも、30倍の速度差が出ている。

それより深刻なのは、evalは実行中のソースコードを書き換えてしまう危険があるということ。上のコードのno warnings 'redefine';を実行してみてほしい。

Subroutine add redefined at (eval 6) line 1.
Subroutine add redefined at (eval 7) line 1.
Subroutine add redefined at (eval 8) line 1.
...

というエラーメッセージが延々と出るはずだ。もし本当にaddというサブルーティンが定義されていたとしたら、元のコードが破壊されてしまう。

evalは最後の武器」というのは、Perlに限らずメタプログラミング可能な言語における鉄則とも言える。たいていの場合、無名関数を生成するだけで同様の目的をより安全かつ高速に行えるのだ。

ちょっと面白いのが、JavaScriptの場合、文字列からfunctionを生成するのに、eval()だけではなくnew Function()を使う手がある。

Run times

eval

var fun;
eval('fun = function(a,b){ return a + b }');
if (fun(1,1) !== 2) alert('whoa!');

new Function

var fun = new Function('a','b','return a + b');
if (fun(1,1) !== 2) alert('whoa!');

function object

var fun = function(a,b){ return a + b };
if (fun(1,1) !== 2) alert('whoa!');

Firefox 3, Safari 3, IE7 ではnew Function()の方が高速なのだが、Opera 9.63とChromeではeval()の方が高速だった。いずれの場合も、差は3割以上と有意だった。

この場合でも、普通にfunctionオブジェクトを生成するのが一番高速なのは全ブラウザー共通であることは想定の範囲内。

大事なことなので繰り返す。メタプログラミングにおいて、

  • eval()は最後の武器
  • それしかない場合に用いよ
  • たいてい、無名関数で事足りる

Dan the Metaprogrammer