本学の情報通信工学科(C科)の学生は1年次後半から2年次前半にかけて、必修のプログラミング演習でPascal言語を習得する。他学科にも同じ科目が開講されるが、Pascalの講義が行われるのはC科のみである。
しかし、来年の学科再編によって流石にもうどの学科からもPascalの講義は無くなるのではないだろうか、と筆者は勝手に予想している。そこで、多くのC科の学生が苦労してきたであろうこの言語に関して、筆者が悩んだ部分や嵌ったところを書いておきたい。
この記事は何らかのプログラミング言語に触ったことのある人向けになると思う。ただ筆者自身がPascal初心者であるため、内容が正しいことの保証はできない。
C科の計算機にはSun Pascalが導入されているので、この環境を前提に記事を書いていく。また自宅で課題を進めるのに便利だった、Windows環境で利用できるFree Pascal(以下FPC)についても一部記述する。
Pascalでは代入演算子に":="を、比較演算子に"="を用いる。一方C言語では代入演算子に"="を、比較演算子に"=="を用いるので紛らわしい。Pascalに触り始めた当初は代入に毎回"="を使おうとしてエラーを出していた。
begin~endは複文と言い、C言語の{~}に相当する。ちなみに普通の文は単純文と呼び、複文はセミコロンで区切られた複数の単純文をまとめてひとつの単純文を作るものであると言える。
問題はbegin~endは{~}に比べて字面が長いので、書いているうちに意外と見辛くなってくるということだ。
インデントの位置は個人の好みだろうが、最終的にbegin~endは1つインデントして、その中身も更にインデントする流儀で落ち着いた。次のような感じである。
if 条件式 then begin 文; 文; ... end;
この書き方ではエディタのタブ幅を2にすると丁度よい感じだった。
上でif文の例を示したが、これにelse文を続ける場合はどうしたらよいだろうか? 他言語の感覚でいくと
if 条件式 then
begin
文;
文;
...
end;
else
begin
文;
文;
...
end;
こんな風に書いてしまいそうだが、これではコンパイルが通らない。この部分の解説が授業の資料になかったためかなり悩んだ。
エラーの犯人は最初のbegin~end直後のセミコロンだ。実はPascalはif~elseをひとつの文と解釈しており、if文の後にセミコロンを置くとそこで文が終了したと見なされてelseが続かなくなってしまう。if~else if~elseの構文においてもセミコロンを置いてよいのは最後のelse文だけである。
Pascalの特徴は1パスコンパイル、すなわちソースを上から1度読むだけでコンパイルできる点にあるらしく、このif~elseの仕様などはまさにコンパイラの都合に合わせられているのではないと筆者は思うのだが、よく知らない。
ちなみにifとelseの中身は本来単純文であるところを、begin~endによって複文にしているだけなので、記述する内容が1行に収まるのならば
if 条件式 then 文 else 文;
のように書いてよい。
前判定反復はとても普通で
while 条件式 do begin 文; 文; ... end;
と、このように素直である。しかし後判定反復がちょっとツンツンしており
repeat 文; 文; ... until 条件式;
のようになっている。whileが単純文をとるのに対して、repeatは初めから複数の文を想定してbegin~endを必要としない点も統一感が感じられないが、何より条件式の解釈が正反対である。
whileは「条件式が真である限り」反復するが、repeatは「条件式が真になるまで」すなわち「条件式が偽である限り」反復する。複雑な条件を書く際はこの部分がとても面倒だった。
この構文自体はPerlなどのdo~until文で触れたことがあったが、大抵はdo~while文とセットで用意されているものなので、後判定反復を使おうとすると必ず否定の条件式を書かなければならないPascalには結構面食らった。ツンツンしているのはrepeat文ではなく、repeat文しか使わせてくれないPascal自身かもしれない。
Pascalには反復を中断する制御構文が標準で用意されていない! 他言語のbreak文のようなものが存在しないのだ。びっくりする。文の構造に厳格な言語であるとは聞いているが、ちょっと厳格すぎやしないだろうか。
では反復を中断したいときにはどうすればいいのだろうか? 授業の資料に載っていたサンプルコードでは、中断条件をすべてwhileの条件式に組み込んでいた。しかしこれでは見通しが悪い。
裏技として、Sun Pascalには反復を抜ける非標準構文exitが存在する。同様にFPCにはお馴染みのbreak文が存在する。
授業において拡張構文などはどの程度使ってよいか分からないが、少なくとも終盤の面倒な課題でこれを使っても怒られることはないと思う。
for文は本当に整数の数え上げと数え下げしかできない。初期化部で複数の変数に初期値を与えることはできないし、条件式に複雑なことを書いたりもできない。なぜならば、for文の構造が
for 変数:= 初期値 to 最終値 do begin 文; 文; ... end;
こんな風になってしまっているためだ。ちなみにこれは数え上げの例で、数え下げの場合はtoではなくdowntoを使う。
致命的なのはPascalがbreakできないことだ。非標準構文を使わずに反復を中断したい場合は、先に書いた通り反復の条件式に中断条件を組み込むしかないのだが、for文の場合はそれさえできない。とても残念である。
結果、課題のコーディングにおいてさえfor文の出番は殆どなかった。
case文はC言語のswitch文に相当する。case文は次のように書く。
case 条件式 of 式:文; 式:文; ... end;
これで条件式と式が一致したところの文が実行される。もちろん文は複文にしてもよい。
しかしここでもPascalは厳格で、case文を使ったが最後、必ずどこかで捕獲しなければいけないというルールがある。どこにも引っかからないままにcase文を抜けてしまった場合はランタイムエラーが起きる。さながらtry~catchで捕らえられる例外の気分だ。この条件式は一体どんな罪を犯したのだろうか。
多岐選択ごときでエラーを出されてはたまらないのでswitch文におけるdefault文のような構文が欲しくなる。Sun Pascalでは非標準構文otherwiseがそれにあたる。FPCでは等価のものとしてelse文が使える。otherwise文を含めたcase文は次のような形になる。
case 条件式 of 式:文; 式:文; ... otherwise 文; end;
otherwiseにはコロンをつけないことに注意したい。
大文字と小文字はまったく区別されない。古い言語では珍しいことでもないが、これを忘れて変数xと変数Xを別物のつもりで扱うと痛い目に遭う。もっともこれは名前を考えることを怠った罰なのかもしれないが。
すべて大文字で書かれたコードを読んでいると、威厳めいたものを感じないこともない。
筆者がSun Pascalにおいて一番驚いたのは文字列の扱いである。正確には文字列変数に文字列を入力した際の挙動だ。これがPascal標準の挙動なのかは分からないが、そうだったらかなり嫌だ。
次の短いサンプルを見てもらいたい。
program sample(input, output); const MAX = 255; var str:packed array[1..MAX] of char; i:integer; begin readln(str); for i:=1 to MAX do writeln(ord(str[i])); end.
これは文字列変数strにキーボードからの文字列を格納したのち、strのメモリの確保されている全範囲に渡って文字コードを出力していくプログラムである。メモリは255文字分確保されており、ここでは文字列格納に使われなかった余りの部分に着目していこう。
FPCでの出力を見ると、余りはすべてヌル文字(0x00)で埋められていることが分かる。しかしSun Pascalでこのプログラムを実行すると、余りはすべて空白文字(0x20)で埋められているという驚愕の事実が判明してしまう。
これはちょっとした脅威である。0x20は別段特別でもない普通の空白文字であって、キーボードのスペースキーを叩けば簡単に入力できてしまう。つまり0x20に出会っても文字列の終端なのか標準入力から受け取ったものなのか全く区別できないのだ。本気で文字列の終端を判定したければ、0x20に出会ったらそこから変数の末尾までが0x20で埋め尽くされているかどうか調べなくてはならない。
唯一の逃げ道は文字列型を使わないことだ。幸いにも非標準組み込み型として可変長文字列が利用できる。Sun Pascalにはvarying型が、FPCにはその名の通りstring型が用意されている。Sun Pascalにもstring型は存在するが、これはサイズ80のただの固定長文字列なので注意が必要だ。
可変長文字列を使うにあたって、Sun PascalとFPCでできるだけコードを変更せずに動かしたい場合は
type {Sun Pascalの場合} vstring = varying[65535] of char; {65535は長さの上限を指定したもの}
type {FPCの場合} vstring = string; {stringに別名を与えた}
このように定義するとvstringの挙動は同じになるので、どちらの環境でも動くプログラムが書ける。
たとえばこんなプログラムを書いたとしよう。
program sample(input, output);
function func(i:integer):boolean;
begin
if i >= 0 then
begin
write('yes');
func := true;
end
else
begin
write('no');
func := false;
end;
end;
begin
func(1);
end.
これは関数funcに1を渡して"yes"と表示されることを期待したプログラムだ。しかしこれはコンパイルが通らない。
Pascalでは文は値を持たない。値を持つのは式だけである。式はそれ単体で文になることはできず、あくまで代入や条件式、関数・手続きの呼び出しに使われる。
ここで問題になるのは関数が戻り値を持っていることだ。値を持つ以上それは式なので、単体で文になることはできない。つまり関数を呼び出す以上、その値を捨てずに何かに使わなければいけないのである。
このあたりが戻り値を持つ関数、持たない手続き、と区別されている所以だろう。
こうしてPascalで躓いた部分を並べてみたが、約一年もの間この言語で課題を解いて来たかと思うと、なんとなく愛着が沸いてくるものである。それまで触ったことがあったのはC言語の系列ばかりだったので、初めのうちこそ拒否反応を起こした(特にbegin~end)ものの、今となってはそれなりに良い思い出といえなくもない。結局は慣れ、住めば都というやつだろう。
Pascalにおいてはネット上で得られる解説が意外なほど少ないのもひとつの特徴かもしれない。元々が教育用の言語であるせいか、どこかの大学の授業資料のようなページばかりが検索にかかる。これはインターネットが普及し始めた当時、プログラミング解説サイトが限られていた状況を彷彿とさせるようである。なので、1から勉強したい人は参考書を買うのが一番早いかもしれない。
また、Sun Pascalを詳しく調べたい人は下の参考文献を読むとよい。解説サイト同様にリファレンスもなかなか見つからないので貴重な情報源となるだろう。
ドキュメントの方は"Sun Workshop Documentation"で検索すると複数の大学で公開されているのが見つかる。ここではMITのURLを掲載した。C科の計算機にも同じものが見つかるが、外部公開はされていないようだ。