camel

鋭い質問です。

Perlの謎(その10)サブルーチンの呼び出し方 - 燈明日記
組み込み関数と同名のユーザ定義関数を定義したときは、どうしても『&』付きでないと呼び出せないのです。

id:chaichanPaPaの主張は、以下のとおり確認できます。

#!/usr/bin/perl
use strict;
use warnings;
sub atan2{
    "atanatan";
}
print atan2(1,1), "\n";

しかし、実際にはビルトイン関数を上書きしているモジュールは少なくありません。たとえばCGI::Carpdie()warn()を上書きしています。

それでは、ビルトイン関数の上書きはどうやるのでしょうか?そして、一旦上書きされたビルトイン関数を呼び出すには一体どうすればいいのでしょうか。

こうすればよいのです。

#!/usr/bin/perl
use strict;
use warnings;
BEGIN{
    *CORE::GLOBAL::atan2 = sub{
        "atanatan";
    };
}
print atan2(1,1), "\n";
print CORE::atan2(1,1), "\n";

BEGIN{}でくくられていることに注目して下さい。これがないとうまく行きません。

Perlでは、ほとんどの外部モジュールは、requireではなくuseされます。use Foo;

BEGIN{
    require Foo;
    Foo->import();
}

と等価ですから、そこで*CORE::GLOBAL::atan2を定義しておけば、ビルトイン関数といえど上書きできるわけです。そして、CORE::atan2を使えば、元のビルトイン関数にはいつでもどこでもアクセスできます。

Perlの謎(その10)サブルーチンの呼び出し方 - 燈明日記
また、現在、組み込み関数と同名がないユーザ定義関数を作って『&』無しで呼んで上手くいっていても、将来、同名の組み込み関数が出来る可能性もあるわけです。

この主張には一理ありますが、三つの理由で&はつかうべきではありません。

  1. ビルトイン関数は滅多なことでは増えない

    Perl 5の最大の特徴は、モジュールを使っていくらでも機能拡張できることにあります。逆に言えば、Perl本体にそれ以上ビルトイン関数を増やすインセンティブは低いのです。このあたりは「まだ若い」JavaScriptや、ビルトイン関数の多さが機能の多さだというユーザーが多いPHPとは違うところです。

    それでもsayのように「あまりに人気があったので」組み込みになったものや、givenのように構文を変えるために付け加えるものがごくたまにあるのは事実です。が、こうした機能追加はまずモジュールで実装され、十分な議論を経てから追加されるので、ある日いきなりある単語がビルトイン関数名になっているということはまずありえません。

    むしろ、Perl 4の負の遺産として、ビルトイン関数が多すぎるというのがモダンPerlモンガーの感想ではないでしょうか。

  2. ビルトイン関数を上書きする方法がすでに用意されている

    前述のとおりです。dump()dbmopen()など、今では「虫垂化」しているビルトイン関数も少なくないのですが、それでもほとんどのビルトイン関数は、必然性があってそうなっているわけです。滅多なことで上書きするべきではありませんし、そして上書きするのであれば確信犯的に、自分が何をしているか知った上でやるべきです。&atan2atan2で区別するというのは上策とは言えません。

  3. 知らないでビルトイン関数と同名の関数を使ったことを教えてくれなくなる

    Perlという言語は、プログラマーの意思の強さを明示性で推し量る言語です。自然言語も含め、どの言語も多かれ少なかれそうなのですが、Perlはとくにその傾向が強い。&を頭につけるということは、「Perlよ、俺は自分のやっていることがわかってるんだからビルトインがあろうがなかろうが俺の言う通りやれ」とPerlに命じることになります。この場合、Perlは折角の警告をひっこめてしまいます。DWIS (Do what I say) は DWIM (Do what I mean)に比べると、プログラマーの責任が大きくなります。

それでも、&が必要な場面が一つだけ残っています。それは、サブルーチンのレファレンスを引数として渡すとき。たとえば、こんな感じ。

#!/usr/bin/perl
use strict;
use warnings;
use Encode;
use utf8;
my $utf8 = '堕落';
sub fallback{
    sprintf "\\x{%x}", shift;
}
print encode('ascii', $utf8, \&fallback), "\n";

このあたりの詳しいことは、「実用Perlプログラミング」の第一章に詳しく乗っています。お勧めです。

Dan the Perl Monger