Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

オブジェクト指向での関数呼び出し

研究内容

ここまで作成してきたsendmail.plライブラリですが、 環境(OSとか)によっては動作しないらしいことが分かりました。 その原因は、本来オブジェクトとして利用すべきJcodeを無理矢理?普通のパッケージとして扱っていたことにあるようです。 ここで、オブジェクトとはなにか、オブジェクトの関数呼び出しの方法を研究して、sendmail.plを修正します。

詳細

エラー原因究明

sendmail.pl を使用したCGIプログラムを作っていたら、次のようなエラーが出てしまいました。

Can't call method "euc" on unblessed reference at (eval 2) line 348.

use CGI::Carp qw(fatalsToBrowser); を使っているので、普通はエラーが出るとファイル名なども表示されるのですが、 それがありません。 エラーの意味は、初めて見たものだったのですが、調べてみると 「eucというメソッドがオブジェクトリファレンス以外で呼び出された」ということのようです。 メソッドとか、オブジェクトとか、今まで扱ってこなかったオブジェクト指向関連の言葉が出てきました。

Perlのオブジェクト指向については、まだ研究していませんのでよく分からないのですが、 エラーメッセージの中に "euc" とあるので、Jcode関連のエラーかなぁというのはなんとなく思いまして、あれこれ調べてみたところ、 なんとか、Jcode.pm の mime_encode 関数の中でエラーが起きていることをつきとめました。

自作の plab::sendmail 関数の mime_encode の呼び出し部分は次の通りです。

local $mime_name = Jcode::mime_encode([\$jis_name]);

Jcode.pm の中の mime_encode は次のような感じ。(Jcode.pm のソースコード

sub mime_encode{
    my $self = shift;
    my $str = $self->euc;
    my $r_str = \$str;
    my $lf  = shift || "\n";
    my $bpl = shift || 76;
    my ($trailing_crlf) = ($$r_str =~ /(\n|\r|\x0d\x0a)$/o);
    $str  = _mime_unstructured_header($$r_str, $lf, $bpl);
    not $trailing_crlf and $str =~ s/(\n|\r|\x0d\x0a)$//o;
    $str;
}

エラーが発生するのは、赤字のところなのですが、 エラーの原因をつきとめなければ修正することもできません。 そこで、これらPerlのコードをきちんと読んでみました。

まず、呼び出し側の引数ですが、[\$jis_name] となっています。 これは、意味としては、 [ ] は名無しの配列への参照(リファレンス)で、 [ ] の中は配列の要素、 \ は変数への参照を得る記号で、 結局 [\$jis_name] は「文字列$jis_nameへの参照を要素に持つ名無しの配列への参照」なのでした。 難しいですね。言い方をかえると「引数は、配列への参照で、要素は文字列への参照」です。

で、これを受け取る mime_encode 側でなにをするかということを見てみました。

sub mime_encode{
    my $self = shift;

shift というのは、配列の最初の要素を配列から取り除き、その要素を返すという関数です。 shift(配列) というようにして使いますが、引数を省略した場合は @_ が対象になります。 @_ というのは、引数に与えられた要素が入っている配列でした。ですから、 ここまでで「$self に 第一引数が入る」ということになります。 第一引数というのは、「配列への参照」でしたね。 それが、$self に入ります。@_ の中は空になります。

    my $str = $self->euc;

ここが問題のエラー箇所です。 $self には配列の参照が入っているんでした。 で、$self->euc としています。 -> というのは、今まで使ったことが無いのですが、 これは、オブジェクト指向における、関数呼び出しというもののようです。 $self というオブジェクトの、euc という関数を呼び出すという意味なのですが、 今、$self には配列の参照が入っています。 なるほど、ここで「Can't call method "euc" on unblessed reference」なわけです。 つまり、この部分は「そのオブジェクトのeucというメソッドを呼び出す」ということなのですが、 ここでは -> の左側が単なる配列の参照で、適切でないために、エラーになっているわけです。 ここまで、なんとなく理解できました。

ここまで読んでみて、これじゃ正しく動かないな、というのは分かったのですが、 逆に、今までちゃんと動いていたのはなんでだろう?ということでした。 今まで使っていたサーバーでは、正しく動いていました。 今回、このエラーが出たのは新しいサーバーです。OSとか、なにか設定とかが違うのでしょうか? 不思議です。。。分かりません。

修正

とにかく原因は分かりました。原因は、関数の呼び出し方が間違っていたということです。 関数の引数は試行錯誤して見つけた「正しいっぽい」ものでしたが、間違っていたようです。 今見てみると、なんで名無しの配列への参照になっているのかとか、思い出せません(^^; ただしい呼び出し方に修正しましょう。答えはずばり

local $mime_name = Jcode->new($jis_name)->mime_encode;

Perlって、初めて見るものって暗号のように見えますが、今回もやっぱり暗号ですね。

最初の Jcode->new は、Jcode パッケージの中の new という関数を呼び出すという意味になります。 これは、意味としては Jcode::new と同じような気がしますが、なにが違うのでしょうか? それは…渡される引数です。Jcode::new とした場合は引数がそのまま関数に渡されますが、 Jcode->new とした場合は、第一引数にそのパッケージ名が自動的に加えられます。次のようなプログラムで確認してみましょう (これはCGIプログラムではありません)

#!/usr/bin/perl

package test;

sub testfunc {
        print "@_\n";
}

test::testfunc(1, 2, 3);
test->testfunc(1, 2, 3);

関数 testfunc は、与えられた引数を表示します。 結果は次のようになります。

1 2 3
test 1 2 3

確かに、-> で関数を呼び出した場合は、第一引数にパッケージ名が追加されていますね。

ここで Jcode に戻りまして、Jcode->new ですが、この中身を確認してみると、次のような関数でした。

sub new{
    my $class = shift;
    my $self  = {};
    bless $self => $class;
    defined $_[0] or $_[0] = '';
    $self->set(@_);
}

my $class = shift; は、第一引数を $class に入れています。 第一引数はパッケージ名ですから、"Jcode" という文字列ですね。 my $self = {}; は、$self を、空の連想配列とします。 { } は無名の連想配列への参照を表す記号です。[ ] が無名の配列への参照を表す記号だったのと似ていますね。 ここでは中身が空ですので、空の連想配列ということです。 bless $self => $class; は、$self を、$class のオブジェクトであると指定します。 $self をパッケージ $class に結びつけるという感じでしょうか。 $class にはパッケージ名 "Jcode" が入っていました。この文で、$self は Jcodeのオブジェクトであるということになります。 このことは、後でまた出てきます。 defined $_[0] or $_[0] = ''; は、見慣れない記法ですが、 defined $_[0] または $_[0] = ''; つまり、 「$_[0] が定義されているか、または $_[0] に '' を代入する」です。 $_[0] というのは、配列の最初の要素ですが、この関数の頭で shift をしていますので、 new に渡された(パッケージ名を除く)1つ目の引数という意味になりますね。 これが存在していなければ '' にするけど、存在していればなにもしないという意味になります。 で、$_[0]ってなに?というと、もともと new 関数が受ける引数は「なにか文字列」です。 文字コード処理をしたい、処理対象の文字列ですね。 最後に $self->set(@_); ですが、ここで -> が出てきましたね。 先ほどの Jcode->new は、パッケージ内の関数の呼び出しの拡張版みたいな感じでしたが、 これはまたちょっと様子が違います。-> の左側が、パッケージ名じゃなくて変数なんですね。 ここで、先ほどの bless $self => $class; が意味を持ってきます。 -> での関数呼び出しは、左側がパッケージ名ならそのパッケージ内の関数を呼び出しますが、 左側が変数(オブジェクト)だった場合は、そのオブジェクトが属しているパッケージの関数を呼び出します。 先ほど、bless $self => $class; というやつで、$self は Jcode パッケージのオブジェクトであるという指定を していますので、これは結局 Jcode 内の set 関数を呼び出すという意味になるんです。 ただし、先ほどのように、第一引数にパッケージ名を追加したりしません。 その代わりに、第一引数にそのオブジェクト自身を渡すんです。 ちょっと複雑ですね。$self->set(@_); とすると、 関数 set は、第一引数に $self、その後ろに @_ を引数として受け取ります。 関数 set は、内部でちょっとゴニョゴニョしまして、$self を戻り値として返します。 関数は、return で明示的に戻り値を返すことができますが、return を使わなかった場合は、 最後に評価した値を返しますので、new 関数自身は、$self->set(@_); の戻り値である $self を返します。 いやー長いですけど、これで new 関数の意味が分かりました。

話を戻しましょう。今研究しているのは次の文でした。

local $mime_name = Jcode->new($jis_name)->mime_encode;

ここまでで、Jcode->new($jis_name) の部分が分かりましたね。 処理対象の文字列である $jis_name を Jcode のオブジェクトに変換しているっていうイメージです。 で、->mime_encode で、mime_encode 関数を呼び出します。mime_encode 関数の中身をもう一度書くと

sub mime_encode{
    my $self = shift;
    my $str = $self->euc;
    my $r_str = \$str;
    my $lf  = shift || "\n";
    my $bpl = shift || 76;
    my ($trailing_crlf) = ($$r_str =~ /(\n|\r|\x0d\x0a)$/o);
    $str  = _mime_unstructured_header($$r_str, $lf, $bpl);
    not $trailing_crlf and $str =~ s/(\n|\r|\x0d\x0a)$//o;
    $str;
}

これでようやく意味が通りました。mime_encode 関数は、-> で呼び出されたことによって第一引数に Jcode のオブジェクトが渡されています。my $self = shift; で、そのオブジェクトを $self に入れています。 エラーが起きていた赤文字のところは、今度はちゃんと意味が分かりますね。 Jcode の関数 euc を、第一引数を $self として呼び出すという意味になります。 そこから後ろは、実際のコード変換の処理ですから今回は確認しません。

なんとなく、分かりました

今まで謎だった、-> という記号を使った関数呼び出し。なんとなく、分かりました。 第一引数に、自動的にパッケージ名とか、オブジェクトを渡してくれるっていう機能だったんですね。 これによって、変数が、自分自身を処理するための関数群(パッケージ名)を知っていて、 自分自身を処理してもらうために、自分自身を第一引数にしてそのパッケージ内の関数を呼び出す、と。 これは、面白いですねー。実は、手元にPerlの本が3冊あるんですが、それにはこのことが少しも 載っていませんでした。オブジェクト指向というのは考え方が難しいですから、 確かに、いきなり出てきたら混乱するだけかも知れません。でも、ここまで勉強してきたおかげで、 なんとなくですが、Perlのオブジェクト指向なコードの書き方が分かってきました。 今度は、自分でこういうものを書いてみようかな?

作成したCGIプログラム

sendmail.pl(変更点のみ記載)
(変更前)
	local $mime_name = Jcode::mime_encode([\$jis_name]);
(変更後)
	local $mime_name = Jcode->new($jis_name)->mime_encode;
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。