レベル: 中級 Teodor Zlatanov (tzz@iglou.com)Gold Software Systems
2001年 1月 01日 Ted Zlatanovは、CおよびJavaプログラマーに対してPerl 5.6が持ついくつかの特性について説明しています。オペレーターのあいまいさの許容、同じことを実行するのに複数の方法が可能であること、句読法、正規表現、変数メカニズムなど、Perl以外のソースから取り入れられたおなじみのフィーチャーに、実際、CおよびJavaプログラマーはうれしい驚きを覚えることでしょう。これらはすべて、プログラマーに多くの選択肢と能力を与えてくれるものです。ポイントとなるのは、Perlとは、各プログラマーが親しんでいる諸分野とかけ離れたものではなく、CやJavaのプログラマーにとっても、ある部分においては便利なものであるという点です。この機会を生かして、あなたのPerl 5.6のスキルをレベルアップさせてください。
熟練のプログラマーでさえ、しばしばPerlには泣かされるものです。これは主に、Perlでは複雑怪奇なコードを簡単に書けてしまうからだと言われています。しかし、Perlがこれほど便利で強力な言語であることや、初めから1つの目的に対して複数の手法を用意することを念頭に設計されていることを考えると、Perlの構造、フィーチャー、考え方からくる混乱を避けることはできません。
今回は、対応するC/C++/Javaのフィーチャーとの比較、対照を通して、さらに複雑なPerl 5.6のフィーチャーをいくつか見ていくことにします。PerlとC、C++、Javaとを最も分かりやすく区別している Larry Wallの論文 "Natural Language Principles in Perl" (この記事の最後にある参考文献を参照してください) で述べられている原理に注目します。Perlの構文の正確な仕組みは、"perldoc perlsyn" マニュアル・ページ、または、最新のPerlに関するベスト・ガイドであるProgramming Perl (参考文献を参照) からより詳しく知ることができます。
インタープリターの仕組み
初心者のPerlプログラマーは、コンパイルというものが見当たらないことにすぐに気付くでしょう。Perlスクリプトは、Perlインタープリター (UNIXシステムでは "perl"、DOS/Windowsシステムでは "perl.exe"、MacOSシステムにはなし) によってその場で実行されます。これを自分の目で確かめてみましょう。Perlインタープリターの名前を入力 (MacOSシステムの場合にはインタープリターを実行) して式を与え、すぐに値を評価することができます。たいてのシステムでは、ユーザー入力の終わりを示すエンド・オブ・ファイル (UNIXの場合Control-D) キー・シーケンスを使用する必要があります。UNIXシステムの場合、以下のスクリプトであれば "5+6" の結果が表示されます。
リスト1.まずは一番小さなプログラムから
> perl
(スクリプト名を指定するまで、Perlはここで入力待ちをします)
print 5+6
ここでControl-Dを押します
11
|
これで、Perlが1行のスクリプトを実行し、評価結果の "11" を画面に表示したところを確認できました。
Perlインタープリターには数多くのオプションがあります。たとえば "-e" フラグは、コマンド行引数をスクリプトとして実行するためのオプションです。したがって、コマンドperl -e'print 5+6' (printコマンド前後の引用符に注意) と上記の小プログラムとは、同じ実行内容であることになります。"-i" フラグを指定すると、フィルター処理を施すようにファイルを適切に編集することができます。"-n" と "-p" スイッチを使うと、ほとんどの場合、インタープリターはプログラマーの指定したアクションを実行する入出力フィルターのような働きをします。"-w" スイッチ (強くお勧めします) は警告をオンにするスイッチで、C/C++ で "-Wall" スイッチを付けてコンパイルするのと似ています。ただし、"-w" スイッチはプログラムの実行中もアクティブである点が異なります。
速度およびベンチマーク・テスト
世間ではPerlとCまたはC++ を比較して、Perlは十分な速さではないということがよく言われます。これには一理あるのですが、CやC++ のプログラムの方が速いと決め付ける前に、Benchmarkモジュール (perldoc Benchmark) を使用してみることをお勧めします。常にそうとは限らないことが分かるでしょう。また、PerlはC/C++ のコードおよびライブラリーへのリンクを得意としている上、sortやprintなどのPerl組み込み関数はたいていの場合、同等のCコードとほぼ同じ速度を実現できます。繰り返しになりますが、どちらが優れているか判断する前にベンチマーク・テストを行うことをお勧めします。
最適化を行う場合は、早まった判断が災いの元であることは覚えておくべきです。プログラムの原型をPerlで書き、それを別の言語で書き直すのはよい方法です。原型とは、すぐに作成できて簡単に破棄できるもののことです。
Javaと比べるとPerlはかなり優秀ですが、それでもベンチマークをお勧めします。Javaは (Perlと異なり) スレッド化に適しているため、スレッド化されるアルゴリズムを実現するのはJavaの方が向いています。しかし、PerlのTk GUIインターフェース・ツールキットは、JavaのSwing GUIライブラリーにやや勝ると考えられます。また、JavaコードはすべてPerlプログラムにリンク可能であり、その逆もまた可能です。そのため、ときにはこれら2つの言語のよいところだけを使うことも可能です。
例外、コンパイル、および文書
PerlではCPANモジュールやeval() 組み込み関数で例外を扱います。eval() は、C++ やJavaのtry/catchブロックで実行されるプログラムの一部とちょうど同じように、コード・ブロックやストリングを評価します。
Perlはスクリプトを実行する前にコンパイルを実行しますが、C/C++/Javaプログラマーが考えているコンパイルとは方法が異なります。これは設計においても動作においても、Javaのバイト・コンパイル処理に極めて近いものです。コンパイルについてのより詳しい情報は、"perldoc perlrun" および "perldoc perlcc" マニュアル・ページにあります。
PODフォーマットを使用すると、Perlプログラムの中に文書を組み込むことができます。これは、API文書に最適化されたJavadocフォーマットほど特化されてはいませんが、組み込みマークアップやセクションを使用できない汎用的なC/C++/Javaのコメントよりは特化されたフォーマットです。
C、C++、Javaと比較しても、Perlプログラムは全くといっていいほど構造化されていません。たとえば、BEGINブロックは最初に実行することになっていますが、1つのプログラムで何度も使用することができます。ネームスペースはどこで開始して、どこで終了しても構いません。定義、変数、関数本体はどこで使用してもよいことになっており、Perlはそれを自動的に判断して最善の対応を取ります。
自由度の高い構造、組み込みコメント、言語全体におけるあいまいさの許容などは便利なものであり、Perlでのプログラム作成を、他のどのプログラム言語よりも、むしろ英語で手紙を書くことに近いものにしています。
言語のあいまいさ
PerlではC/C++/Javaよりもあいまいさの許容範囲が広くなっています。たとえば、コンマでステートメントおよび関数のパラメーターを区切ることができます。
リスト2. ステートメントまたは関数のパラメーターを区切る
print 'Hello', ' ', 'there.', "\n"; # print "Hello there\n"
foreach (1..10)
{
my $i;
$i = $_ * 2, print "$i\n"; # print evens from 2 to 20
}
|
Perlは、ときにはうまくいかないこともありますが、可能な限りあいまいさを取り除きます (これがPerlと英語のかなり似ている点です)。
Perlのあいまいさの許容を示すもう一つの代表的な例として、しばしば変数が暗黙的に使用されることが挙げられます。たとえば、"print" はそれ単独で $_変数の内容を表示します。これは、オペレーションがあいまいである場合、通常は $_変数をデフォルトとする、と理解すると合点がいくと思います。たとえば、次のようになります。
リスト3. 暗黙的に使用される変数
$_ = "hello";
s/hello/hi/; # $_ is "hi" now
print; # prints "hi"
|
デフォルトの変数を使用したときに、コードがどれほどすっきりするか分かるでしょう。あいまいさを許すことで式を短くできるのが、Perlと英語の共通点です。
1つの目的に対して複数の手法が存在する (There's more than one way to do it (TMTOWTDI))
各言語には、それぞれ特有の語法があります。Cの場合、一定範囲の数値に対して繰り返し処理を行うための最良の手段は、for() ループの使用です。Javaの場合、静的メソッドはインスタンス名ではなくクラス名を使って呼び出さなければなりません。
Perlの場合、なにをするにしても、少なくとも2つの手法が用意されています。TMTOWTDIは、この言語の基本原理となるものであり、Perlコミュニティーではさまざまな手段を提供することが、許されているというよりもむしろ積極的に奨励されています。
ある配列を表示する場合を例に取りましょう。次に示す式は、すべて同じ処理を実行します。
リスト4. 配列を表示する
print foreach @array;
foreach (@array) {print};
map {print} @array;
print @array;
|
上記のコードを理解することは、すべてのPerlコードを理解することにつながります。どれが正しいのかなどと気にする必要はありません。正しい手法はいくつもあるのです。異なるアプローチに目を向け、この言語に対する理解を深めてください。
とは言うものの、複数の手法があるということは、すなわちまずい手法がないということにはなりません。誤ったコードを作成してします可能性の方が、正しいコードを作成する可能性よりも常に高いのです。コードは読みやすくし、自作の関数を使用せずにPerlの組み込み関数を使用して、方法はあいまいでも目的は明確になるように記述すべきです。
正規表現の泣き所
初心者にとって、Perlの正規表現は取り付きにくいものです。これらはまるで、文字と記号の寄せ集めのような感じです。普通に考えれば、これらの正規表現は実際には、何年も昔にカラハリ民族が世界中の大学のコンピューター・サイエンス・プログラムに侵入して作り上げたのではないかと 信じずにはいられないほどです。
Perlの正規表現は、シェル・スクリプトおよびawk/grepツールを受け継いだものです。しかしながら、その言語としての可能性はオリジナルをはるかにしのいでいます。
基本的な正規表現は、書くのは簡単ですが読むには少々てこずります。たとえば、"con\w+" という正規表現は "contra" および "contrary" に一致し、"pro" または "con" には一致しません。しかし、Perl 5.6.0の正規表現はさまざまな改善を施されています。ユニコード文字クラス指定子、パターン内での任意のコード実行、フラグの切り替え、条件式、その他もろもろのフィーチャーが正規表現エンジンに追加されました。
初心者への最良のアドバイスとしては、最初のうちは基本的な正規表現 (参考文献または "perldoc perlre" マニュアル・ページを参照) を習得するようにし、高度なフィーチャーは後回しにすることです。正規表現は調教の難しい獣のようなものです。通常はコメントもないままぎっしりと書き込まれていることが多く、Perlコードの中でもとりわけ判読しにくい部分です (コメントは使用可能ということにとどまらず、実動コードの作成者すべてに強く推奨されるものです)。
正規表現はC/C++/Javaにおいても外部パッケージとして使用できますが、Perlのそれは、正規表現を使用した検索および置換ツールとしては、今日手に入るものの中で圧倒的に優れたものです。純粋なCによるアプローチよりも処理が遅くなることがまれにありますが、純粋に正規表現指向の問題については、Perlこそが最初に検討されるべきツールです。
スカラー、配列、ハッシュ: 驚きの高機能
C、C++、Javaの変数と異なり、Perlの変数は自動的にインスタンスの生成と型指定を行います。初心者のPerlプログラマーにとっては信じられないことかもしれませんが、一度理解しさえすれば、大変に便利な機能です。
すべての実動コードでは "use strict" プラグマを使うことをお勧めします。このプラグマは、数あるプラグマの中でも特に、変数を使用する前に必ず宣言されるようにするためのものです。それによって、絶えず悩まされているタイプミスによるバグを防ぐことができます。
"use strict" を使用しないと、以下の類の問題が持ち上がることがあります。
リスト5. よくあるタイプミス
$i = 5;
print $j; # print $i
|
プログラマーが、iと打つべきところを間違ってjと打ってしまったケースです。Perlはこれを正しいものと見なし、$jの値を表示します (つまりなにも表示しません)。インスタンスの自動生成はときには便利ですが、個人的な経験から言わせてもらうと、個人で使用するコード以外はすべて "use strict" によってこの機能をオフにするのが賢明であると思います。
Perlの変数には、スカラー、配列、ハッシュがあります (このほかの変数もあるのですが、直接目にすることはあまりないでしょう)。これらにはリファレンス機能もありますが、それでもただのスカラーです。スカラーの名前は "$" で、配列の名前は "@"、そしてハッシュの名前は "%" で始まります。
スカラーは頻繁に登場します。スカラーは単一の値を持っており、その値はストリングまたはリファレンスです。Perlは必要に応じてストリングを数値に変換します。初心者のPerlプログラマーにとって、これは驚きを禁じえないことでしょう。例を見てみます。
リスト6. スカラー
$i = "hi there";
print 1+$i; # prints 1
|
スカラー $iには、数値0を持つストリング "hi there" が含まれます。よって、1 + "hi there" の値は1になります。
ストリングと数値の2つがあると考えないでください。メモリーにあるのはスカラーだけであり、これには1つのスカラー値があるだけです。この値は数値上のコンテキスト (加算) では数値となり、ストリングのコンテキスト (表示) ではストリングとなります。しかし、存在するのはたった1つの値であるということになります。
未定義のスカラーには値 "undef" が含まれます。C/C++/Javaでヌルとなにかを比較するのと同じように、undefとなにかを比較することはありません。代わりに、次のようにdefined() 関数を使用します。
リスト7. 'defined()' 関数の使用
$i = "hi there";
print $i if defined $i; # prints "hi there"
undef $i; # set $i to be undef
print $i if defined $i; # prints nothing
|
配列とは、スカラーのリストのことです。これはJavaにおけるVectorクラス同様、必要に応じて自動的にサイズ変更されます。CおよびC++ には、配列に相当するものはあらかじめ組み込まれてはいませんが、似たような機能を備えたSTLなどのライブラリーが数多く用意されています。配列の持つ興味深い特性としては、スカラーのコンテキストにおいて、これらが配列中のエレメント数を表すということが挙げられます。
リスト8. 配列中のエレメントの数
@a = ("hi there", "nowhere");
print scalar @a; # prints 2
push @a, "hello"; # add "hello" at the end
print scalar @a; # prints 3
|
ハッシュは配列に似たものではありますが、位置によってスカラーを配列しないところが違います。ハッシュのスカラーには、別のスカラー (固有のキー) を使ってインデックスが付けられます。たとえば、社会保障番号 (かなり固有なキーです) によってインデックスを付けられた名前のリストなどはハッシュにすることができます。ハッシュにキーを追加すると、ハッシュの大きさは自動的に調整されます。ハッシュは、JavaにおけるHashMapクラスまたはHashtableクラスのようなものです。
スカラーの中にはリファレンスがあり、これによりなんでも参照することができます。したがって、ハッシュの配列、配列のハッシュ、ハッシュのハッシュ、そして配列の配列 (N次元配列) を作成することができます。リファレンスの内容にアクセスするには、明示的にリファレンス解除するか、"->" 演算子を使用するかによって幾つかの方法があります。これはかなり広がりのある話題ですので、詳しい情報は "perldoc perlref" マニュアル・ページを参照してください。
CおよびC++ では、スカラーが唯一の組み込み型です。そのため、プログラマーは配列やハッシュを使いたいと思ったときに、STLなどの外部ライブラリーを使用するなどして、多くの苦労を重ねてきました。
Javaには配列やハッシュに対応したさまざまな組み込み型が用意されましたが、これらはJava言語自体において暗黙的な扱いではありません。たとえば、ハッシュのキーに対して繰り返し処理を施す場合、Perlに比べてJavaでは約3倍のタイピングが必要になります。
リスト9. Javaにおける、ハッシュのキーに対する繰り返し処理
import java.util.Enumeration;
import java.util.Hashtable;
Hashtable hi = new Hashtable();
// fill in hi's values
// we can use an Iterator, still a lot of typing
for (Enumeration enum = hi.elements();
enum.hasMoreElements();)
{
Object o = enum.nextElement();
// do something with o
}
|
リスト10. Perlにおける、ハッシュのキーに対する繰り返し処理
# note that this even includes the definition and initialization of
# the hash, and still is more compact than the Java code!
%hash = { a => "hi", b => "hello" };
foreach (values %hash)
{
# do something with $_
}
|
Perlにない機能
C、C++、JavaにあってPerlにないフィーチャーというものが数多くあります。つまりは、別々の言語であるということです。これらのフィーチャーの中には、Javaの単一継承モデルとC++ の多重継承モデルのように、互いに直接競合してしまうものがあります。このような場合、両方を取ることができないのは明らかであり、そのためPerlでは独自の方法を採用しています。
PerlプログラムはCライブラリーにリンクできるため (実際、Perlの多くの機能がこの方法によってインプリメントされています)、CまたはC++ のコードにできることで、Perlがリンクを使って実現できないことはほとんどありません。ここでは、外部へリンクする件についてはひとまず横へ置き、この言語に組み込まれている機能に話題を限定して議論を進めることにしましょう。
CおよびC++ と比較すると、Perlは実行速度の点で劣ることがあります。これも一つの問題ではありますが、多くの場合、プログラミングを工夫したり、Perlに組み込まれている機能を使用することによって簡単に解決することができます。
また、PerlではCおよびC++ のライブラリーを直接使用することはできません。これらに含まれる定数や機能は、モジュールやさまざまなバインディングを駆使してPerlに適合させねばならず、その結果、開発が遅れたり、実行速度が低下したりすることが考えられます。現在までにCPAN上で膨大な量のバインディング用モジュールが公開されているため、最近では、このことはあまり問題にならなくなっています。
Perlはプログラマーのスキルとしては、CやC++ ほど広く受け入れられてはいません。Perlは若い言語であり、支持者を増やしてはいますが、ごく一般的に使われるまでには至っていません。しかし、PerlはたいていのUNIXシステムにインストールされており、また、Perlが移植されていないオペレーティング・システムはほとんどありません。
Perlでは単一継承階層や多重継承階層、カプセル化、ポリモアフィズムをサポートしていますが、これらは外部モジュールを使用しないと利用できませんし、また、プログラマーに同意してもらうことも必要です。言いかえれば、言語自体が厳密なOOP規則を課すことはありません。規則を順守するかどうかはプログラマー次第です。プログラマーとプロジェクトの出来いかんで、結果が左右されることになります。
Perlのスレッドおよびユニコード・サポートは、Javaに比べてかなり遅れており、C/C++ と比べてもやや遅れています。Javaは当初からスレッドおよびユニコードをサポートするよう設計されていますし、一方C/C++ では、Perlよりさらにこれらのフィーチャーを必要としていることから、より長い年月をかけてその整備に当たってきました。Perlにおけるスレッドおよびユニコード・サポートは未だに試験的な段階にありますが、5.6.0に次ぐ正式リリースでは、事情が変わってくるでしょう。
Perlの最も優れている部分とは
C/C++/Javaプログラマーにとって、より優れた部分を持つPerlは非常に価値のあるものです。たとえば、Perlにおいて正規表現はごく当たりまえの機能ですが、C、C++、Javaにおいては極めて実行が困難です。関数の引数を暗黙的に使用できること、そして構文やプログラム構造の自由度が高いことが、Perlをさらに魅力的なものにしています。
Perlは万人向けではありません。進んで慣れようとする姿勢と、その弱点を受け入れる覚悟、そして言うまでもなく有益なアプリケーションが必要になります。ただ魅力的であるからという理由だけでPerlを使用すべきではありません。それが他よりも優れたツールだからこそPerlを使用すべきなのです。C、C++、そしてJavaの方が適しているのであれば、そちらを使用すべきです。優秀なプログラマーは、常に複数のツールを用意しておくものです。
Perlにはいくつかのささいな弱点もありますが、開発者のたゆみない努力があれば常に解決することができます。スレッド、ユニコード・サポート、そして厳密なOOP規則が必要であれば、それら固有の必要性に合ったものとして、Perl以外の言語を検討して見るのもよいでしょう。
Perlは汎用的な言語です。そして、数多くの異なるモジュールを結び付ける役割を果たす柔軟な言語です。また、どのような手続き型アルゴリズムまたは関数型アルゴリズムでもインプリメントすることができます。ハッシュ・エレメントに対する繰り返し処理など、1つのことを行うのにより短いコードで済むPerlは、開発サイクルを大幅に短縮してくれます。しかし最も大切なのは、Perlを使ったプログラミングは楽しく、常になにかを学んでいけるということです。
参考文献
著者について  | |  | Teodor Zlatanovは1999年にボストン大学を卒業し、コンピューター・エンジニアリングで学位を取得しています。1992年以来プログラマーとして働いており、Perl、Java、C、C++などの言語を使用してきています。関心を持っている領域としてはオープン・ソース作業、Perl、テキスト構文解析、3層のクライアント/サーバー・データベース・アーキテクチャー、Unixのシステム管理などです。助言や間違いの指摘を歓迎しています。連絡先はtzz@bu.eduです。 |
記事の評価
|