Perlのエラーメッセージのコーナー。
Perlのエラーメッセージが表示される最小のプログラムを書いて、どういうときにどう怒られるかを研究する。
今回は
%s package attribute may clash with future reserved word: %s
を研究する。

Perl (14133000834)
これは、

(主語S):%s package attribute
(動詞V):may clash
(修飾子M):with future reserved word: %s

という第2文型の文で、

(主語S):%s パッケージ属性は
(動詞V):衝突する可能性がある
(補語C):将来の予約語と

だから
%sパッケージ属性は将来の予約語とは衝突する可能性がある
という訳になる。

clashという動詞はぶつかる、衝突する、その結果ガッチャーンと音がするという意味だ。
crashという動詞もまったく同じ意味がある。
(crashは名詞の「衝突、とどろき」という意味もある。)
日本人が間違えるLとRだが、英語でもLとRで同じような言葉があるのが面白い。

これはサブルーチンの属性というものについてのメッセージである。

前回は、サブルーチンがlvalue属性になれるということを研究した。
lvalue属性を指定すると、substr関数のように代入の左辺に来るサブルーチンが掛けるが、指定しないで左辺に書くと怒られる。

さて、この属性だが、ユーザーが自由に定義できる。
では、使ってみよう。
#! /usr/local/bin/perl
#
# attrTest.pl -- 属性のテスト(1)存在しない属性を指定すると怒られる

use 5.010;
use strict;
use warnings;
use utf8;

&test_sub("Yeah!");

sub test_sub : Sonna_Zokusei_nai {
 my $str = shift;
 say "test_sub: str:", $str;
}
上のプログラムでは、サブルーチン「test_sub」に、属性「Sonna_Zokusei_nai」を渡した。
そんな属性はないので、怒られる。
[perl]$ attrTest.pl
Invalid CODE attribute: Sonna_Zokusei_nai at /Users/query1000/perl/attrTest.pl line 14.
BEGIN failed--compilation aborted at /Users/query1000/perl/attrTest.pl line 14.
無事怒られた。
(無事怒られるってなんだよ!)

では、属性を定義するにはどうすればいいのだろうか。
次のプログラムでは、MODIFY_CODE_ATTRIBUTESという関数を書いてみた。
#! /usr/local/bin/perl
#
# attrTest.pl -- 属性のテスト(2)MODIFY_CODE_ATTRIBUTES関数を書いてみた

use 5.010;
use strict;
use warnings;
use utf8;

sub MODIFY_CODE_ATTRIBUTES {
 my ($pkg, $ref, @attr) = @_;
 say "MODIFY_CODE_ATTRIBUTES ran!!!";
 say " pkg:", $pkg;
 say " ref:", $ref;
 say " attr:", @attr;
 return;
}

&test_sub("Yeah!");

sub test_sub : Sonna_Zokusei_nai {
 my $str = shift;
 say "test_sub: str:", $str;
}
実行してみる。
[perl]$ attrTest.pl
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7ffe58835d08)
attr:Sonna_Zokusei_nai
test_sub: str:Yeah!
なんとなく動いた。

MODIFY_CODE_ATTRIBUTEというのは、Perlによって決められた固定のサブルーチン名で、この名前を変えることは出来ない。
このサブルーチンは、プログラムの中でsub宣言を行って、属性付きのサブルーチンを定義した瞬間に実行される。

他に、スカラー変数をmy宣言した瞬間に走るMODIFY_SCALAR_ATTRIBUTE、配列をmy宣言した瞬間に走るMODIFY_ARRAY_ATTRIBUTE、ハッシュをmy宣言した瞬間に走るMODIFY_HASH_ATTRIBUTEがある。

MODIFY_CODE_ATTRIBUTEは、サブルーチンのlvalue属性を定義したことによって左辺値に使えるようになったように、サブルーチンの何らかの属性を与えることが出来るが、本稿ではそこまで行かない。
(実はまだ分かっていない。)
他にFETCH_CODE_ATTRIBUTESという属性を取得する関数も作れる。

MODIFY_CODE_ATTRIBUTEを呼ぶと、引数リスト@_に順番にサブルーチンのカレントパッケージ、サブルーチンのリファレンス、サブルーチンの属性のリストが入ってくる。

なお、この関数は、サブルーチンの定義に先んじて行われなければならない。
BEGINブロックで陽に囲んでもダメである。

上では「Sonna_Zokusei_nai」という属性を指定したので、それが表示された。

えっ、でも、そんな属性ないはずなのに?

もう1個増やしてみる。
#! /usr/local/bin/perl
#
# attrTest.pl -- 属性のテスト(3)属性を増やす

use 5.010;
use strict;
use warnings;
use utf8;

sub MODIFY_CODE_ATTRIBUTES {
 my ($pkg, $ref, @attr) = @_;
 say "MODIFY_CODE_ATTRIBUTES ran!!!";
 say " pkg:", $pkg;
 say " ref:", $ref;
 say " attr:", @attr;
return;
}

&test_sub("Yeah!");

sub test_sub : Kono_Zokusei_aru Sonna_Zokusei_nai {
 my $str = shift;
 say "test_sub: str:", $str;
}
Sonna_Zokusei_naiの前にKono_Zokusei_aruを増やしてみた。実行する。
[perl]$ attrTest.pl
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7ffe31035d08)
attr:Kono_Zokusei_aruSonna_Zokusei_nai
test_sub: str:Yeah!
どちらも通ってしまった。

さて、現状ではMODIFY_CODE_ATTRIBUTEは末尾に単にreturnと書いていて、戻り値にundefを返している。
ここでなんとなく文字列を返すとどうなるだろうか。
#! /usr/local/bin/perl
#
# attrTest.pl -- 属性のテスト(4)MODIFY_CODE_ATTRIBUTEで戻り値を返す

use 5.010;
use strict;
use warnings;
use utf8;

sub MODIFY_CODE_ATTRIBUTES {
 my ($pkg, $ref, @attr) = @_;
 say "MODIFY_CODE_ATTRIBUTES ran!!!";
 say " pkg:", $pkg;
 say " ref:", $ref;
 say " attr:", @attr;
 return "bad";
}

&test_sub("Yeah!");

sub test_sub : Kono_Zokusei_aru Sonna_Zokusei_nai {
 my $str = shift;
 say "test_sub: str:", $str;
}
実行してみる。
[perl]$ attrTest.pl
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7f9f49035cc0)
attr:Kono_Zokusei_aruSonna_Zokusei_nai
Invalid CODE attribute: bad at /Users/query1000/perl/attrTest.pl line 25.
BEGIN failed--compilation aborted at /Users/query1000/perl/attrTest.pl line 25.
間違ったコード属性「bad」ということで怒られた。
これは、MODIFY_CODE_ATTRIBUTESを書かなかった時と同じコンパイルエラーである。

これは、ここまでに、定義したい属性があれば正常にreturnしてしまって、定義していない属性を指定されたらその名前を返すという書き方をするといいはず。
#! /usr/local/bin/perl
#
# attrTest.pl -- 属性のテスト(5)ない属性の時だけ属性名を返す

use 5.010;
use strict;
use warnings;
use utf8;

sub MODIFY_CODE_ATTRIBUTES {
 my ($pkg, $ref, @attr) = @_;
 say "MODIFY_CODE_ATTRIBUTES ran!!!";
 say " pkg:", $pkg;
 say " ref:", $ref;
 say " attr:", @attr;

 for my $attr (@attr) {
  if ($attr eq "Kono_Zokusei_aru") { # この属性はある!
   say "do anything good!";
   return;
  } elsif ($attr eq "Kono_Zokusei_mo_aru") { # この属性もある!
   say "do anything good too!";
   return;
  } else { # ない属性を指定されると・・・
   return $attr;
  }
 }
}

&test_sub1("Yeah!");

sub test_sub1 : Kono_Zokusei_aru { # ある属性1を指定
 my $str = shift;
 say "test_sub1: str:", $str;
}

sub test_sub2 : Kono_Zokusei_mo_aru { # ある属性2を指定
 my $str = shift;
 say "test_sub2: str:", $str;
}

sub test_sub3 : Kono_Zokusei_nai { # ない属性を指定
 my $str = shift;
 say "test_sub3: str:", $str;
}
実行する。
[perl]$ attrTest.pl
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7fe591836278)
attr:Kono_Zokusei_aru
do anything good!
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7fe5918311d8)
attr:Kono_Zokusei_mo_aru
do anything good too!
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7fe591831088)
attr:Kono_Zokusei_nai
Invalid CODE attribute: Kono_Zokusei_nai at /Users/query1000/perl/attrTest.pl line 46.
BEGIN failed--compilation aborted at /Users/query1000/perl/attrTest.pl line 46.
できた。

さて、この属性名だが、Kono_Zokusei_aruという名前を、konozokuseiaruと変えてみる。
#! /usr/local/bin/perl
#
# attrTest.pl -- 属性のテスト(6)属性名を小文字化してみる

use 5.010;
use strict;
use warnings;
use utf8;

sub MODIFY_CODE_ATTRIBUTES {
 my ($pkg, $ref, @attr) = @_;
 say "MODIFY_CODE_ATTRIBUTES ran!!!";
 say " pkg:", $pkg;
 say " ref:", $ref;
 say " attr:", @attr;

 for my $attr (@attr) {
  if ($attr eq "konozokuseiaru") { # この属性はある!(小文字化)
   say "do anything good!";
   return;
  } elsif ($attr eq "Kono_Zokusei_mo_aru") { # この属性もある!
   say "do anything good too!";
   return;
  } else { # ない属性を指定されると・・・
   return $attr;
  }
 }
}

&test_sub1("Yeah!");

sub test_sub1 : konozokuseiaru { # ある属性1を指定(小文字化)
 my $str = shift;
 say "test_sub1: str:", $str;
}

sub test_sub2 : Kono_Zokusei_mo_aru { # ある属性2を指定
 my $str = shift;
 say "test_sub2: str:", $str;
}

#sub test_sub3 : Kono_Zokusei_nai { # ない属性を指定(いったんお休み)
#  my $str = shift;
#  say "test_sub3: str:", $str;
#}
実行する。
[perl]$ attrTest.pl
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7fe221836278)
attr:konozokuseiaru
do anything good!
CODE package attribute may clash with future reserved word: konozokuseiaru at /Users/query1000/perl/attrTest.pl line 36.
MODIFY_CODE_ATTRIBUTES ran!!!
pkg:main
ref:CODE(0x7fe2218311d8)
attr:Kono_Zokusei_mo_aru
do anything good too!
test_sub1: str:Yeah!
ようやく今回目的の警告メッセージが表示された。

っていうかここに辿り着くまでの話が長いよ!!!

これは、属性名を小文字だけにしたことによって起こる警告である。

属性名は、Perlによって将来いくらでも拡張されるおそれがある。
それで、ユーザーが自分だけのために作る属性は、大文字小文字混じりにすることが推奨されており、小文字だけで書いたら怒られる。
ということは逆に、Perlの開発者は小文字だけで属性を追加するということであろう。

これ、アンダースコア(_)を1文字でも使うと警告を免れる。
あと、面白いが、漢字だと警告されない。

さて、上記の研究だけでは、属性というものをわざわざ定義する意味が分からない。
どのサブルーチンからでも呼べる共通の処理が走るだけであるが、それならサブルーチンからサブルーチンを呼べばいいだけに思える。

新しい属性としては、Attribute::Protectedというものが便利で、CPANでソースが公開されている。
これを使うと、private、protected、publicというJavaのようなアクセス修飾子が使える。
なお、わざわざMODIFY_CODE_ATTRIBUTESを作らなくても、Attribute::Handlersというモジュールを使うともっとカンタンに出来るようだ。

詳しくは、以下のブログに書かれているので、機会があったら勉強したいと思う。
(ひどい>自分)

Attribute::Protectedのソースを読んでみました! - 燈明日記

とりあえず目的の診断メッセージが表示できてぼくは満足である。
ていうかこの連載、思いのほか面倒だ。