組み込み関数のオーバーライド

useとかrequireとかExporterの復習にプログラミングPerlのモジュールの章(11章)を読んでいたら、組み込み関数のオーバーライドという変り種があって、試して遊んでみたのでメモしておきます。

まず、このように単にchdirを前宣言しただけでは

use Cwd;
use Perl6::Say;
sub chdir {
    my $dir = shift;
    say "moving to ",$dir;
    CORE::chdir $dir;
}
chdir "/var/log";
say Cwd::getcwd;

当然、エラーになります。

Ambiguous call resolved as CORE::chdir(), qualify as such or use & at ./test.pl line 18.

CORE(組み込み関数にアクセスするための擬似パッケージ)のchdirなのかここで定義しているchdirなのかがあいまいな呼び出しだと怒られています。


ここで、

&chdir("/var/log");
say Cwd::getcwd;

あるいは

main::chdir("/var/log");
say Cwd::getcwd;

とすれば、上記のエラーは回避されますが、これは単にここで定義したchdirというサブルーチンを呼び出しているだけです。

つまり、組み込み関数のchdirをオーバーライドしている(名前を汚染している)わけではなくて、mainパッケージのレキシカルスコープにあるchdirというサブルーチンを呼び出している状態です。


組み込み関数のchdirをオーバーライドするためにはsubsプラグマを利用して、chdirの名前をimportしてオーバーライドしてやります。

use subs qw( chdir );
chdir "/var/log";
say Cwd::getcwd;

・・・「chdirの名前をimportしてオーバーライドしてやる」って、自分で書いててよくわからなくなりそうな文章ですが、Perlにおけるオーバーライドはimportしてきた名前の型グロブに自パッケージで宣言したサブルーチンのコードリファレンスを代入することで実現しているようです。

つまり、ここでのchdirのオーバーライドは内部的には

* chdir = \main::chdir;

ということになります。


ついでにimportについて再度復習。importの説明は以下のエントリが丁寧かつわかりやすいです。

perl の import の働き - adiary開発日誌
http://adiary.blog.abk.nu/0148

importとは何かを一言でいうなら、同エントリからの以下の引用が端的でわかりやすいと思います。

import は外部ライブラリの関数をあたかも自分の関数のように扱う仕組みです。正確に言えば、外部ライブラリの関数を自分の名前空間内に展開する仕組みが import です。

つまり、上記のオーバーライドの例は厳密にはmainパッケージ内に(subsの内部でのimportによって)chdirという名前を展開し、その名前の型グロブにmainパッケージで宣言したサブルーチンであるchdirのコードリファレンスが代入されているという事になります。

そのため、同じファイルに別のパッケージをつくってみると、そこで実行したchdirは名前が汚染されていないため、CORE::chdir(オーバーライドされていない組み込み関数のchdir)が呼び出されている事がわかります。

package main;
use Cwd;
use Perl6::Say;
use subs qw( chdir );
sub chdir {
    my $dir = shift;
    say "moving to ",$dir;
    CORE::chdir $dir;
}
chdir "/var/log";
say Cwd::getcwd;

package Hoge;
use Cwd;
use Perl6::Say;
chdir "/var/log";
say Cwd::getcwd;

組み込み関数のオーバーライドを使う機会はそうそうないでしょうが、importの基本的な概念などはしっかり理解しておく必要がありますね。