Composition with Grid IX by Piet Mondrian, 1919. https://www.wikiart.org/en/piet-mondrian/composition-with-grid-ix-1919
変数の次は「繰り返し」について学びます。 繰り返し処理は、プログラミング用語ではループ(loop)と呼ばれています。 前の章で、変数とその使い方について学びましたが、 ループにおいても変数は重要な役割を果たします。
変数を使いこなすために重要な本質的な考え方は、処理の抽象化でした。 具体的な値ではなく変数を使って抽象的に考える事により、 プログラムを変更することなく様々な描画に使用できることを学びました。 プログラムで変更する場所は変数の値(初期値)を設定する部分だけであり、 そのコード片を変更するだけで異なる絵を描くことができるようになりました。
この章では、変数の値をプログラムにて変更する方法を学びます。 これにより、プログラムの効果を何十倍や何百倍・何億倍などなど、 好きなだけ拡大できるようになります。
ループの必要性を体験する
本章でも変数のときと同様、 まずはループの必要性を体験するところから始めていきましょう。 冒頭に挙げた絵を参考に、格子状に線を描いてみましょう。 例えば、400 × 300 のキャンバスに、40 × 30 の大きさの格子を書くとしましょう。 その場合、以下のようなプログラムが考えられます。
// draw grid ver.0
size(400,300);
background(255,255,255);
strokeWeight(3);
// draw vertical lines
line( 0,0, 0,400);
line( 40,0, 40,400);
line( 80,0, 80,400);
line(120,0, 120,400);
line(160,0, 160,400);
line(200,0, 200,400);
line(240,0, 240,400);
line(280,0, 280,400);
line(320,0, 320,400);
line(360,0, 360,400);
line(400,0, 400,400);
// draw horizontal lines
line(0, 0, 400, 0);
line(0, 30, 400, 30);
line(0, 60, 400, 60);
line(0, 90, 400, 90);
line(0,120, 400,120);
line(0,150, 400,150);
line(0,180, 400,180);
line(0,210, 400,210);
line(0,240, 400,240);
line(0,270, 400,270);
line(0,300, 400,300);
このプログラムを実行すると以下のような絵が得られます:
縦線・横線、それぞれ 11 個の line 関数にて格子のための平行線が描けました。 でもこれ、似たようなコード片だらけで少々冗長な感じがします(注1)。 どの line 関数も縦線か横線を書いているだけなので、変数を使って抽象化を試みてみます。 例えば縦線を書く場合、描画位置の x 座 標を px という変数で表すと以下の様に書けます。
注1:プログラムの場合、似たようなコード片はあまり良い兆候ではありません。 このプ ログラムをみて「面倒くさいなぁ」と思った人は、 プログラマとして良い感性を持っているのかもしれません。
変数を使った line 関数部分を抽象化したプログラムの例:
// draw grid ver.1
size(400,300);
background(255,255,255);
strokeWeight(3);
// draw vertical lines
int px=0;
line(px,0, px,400); px=40;
line(px,0, px,400); px=80;
line(px,0, px,400); px=120;
line(px,0, px,400); px=160;
line(px,0, px,400); px=200;
line(px,0, px,400); px=240;
line(px,0, px,400); px=280;
line(px,0, px,400); px=320;
line(px,0, px,400); px=360;
line(px,0, px,400); px=400;
line(px,0, px,400);
// draw horizontal lines(省略)
無事、縦線に関する line 関数部分は全て line(px,0, px,400); という プログラム片に統一されました。 しかし、変数 px については具体的な値を毎回設定しているので、 抽象化されているとは言えません。 もう、これ以上、抽象化することはできないのでしょか?
…とまあ、こんな風に書いてあったら大体はまだ抽象化できるわけです。 今回の場合、px に代入される数に着目してみましょう。 px は格子横 幅の分(40 ピクセル)づつ増加していっています。 例えば、今 px に 80 と いう値が代入されているとします。 次はこの値に 40 を加えた値を px とできれば良いわけです (その場合、px は 80+40 で 120 という値になります)。 ちょっと数式っぽい書き方をすると、px + 40 → px という感じでしょうか (矢印は、ある値を変数に代入することを意味しています)。
この矢印による表現(表記)を使うと、左右逆にしてこんな風にも書けます :px ← px + 40。 なぜこのように左右を入れ替えて記述するかというと、 多くのコンピュータ言語では左辺に右辺を代入するという形式を取るからです。 そして、代入を意味する記号として = を使用するものが多い状況となっています。 Processing で使用している Java というプログラミング言語でもそうです。 なので、px ← px + 40 は px=px+40 と記述されます。
記号 = を数学でいうところの「等号」と解釈してしまうと、非常に混乱してしまいます。 この場合の = という記号は、 左辺と右辺が等しいことを示す等号ではなく、 右辺を左辺に入れる(設定する)「代入」の意味であると読み替えるようにして下さい。 ちなみに、等号を表す記号は Java では == と = を 2 つ連続して表します。 プログラミング言語の世界では = と == は異なることにも注意して下さい。
さて、このように px の値を更新する方法を学んだので、 改めてpx の値を設定する部分も含めて抽象化してみます:
// draw grid ver.2
size(400,300);
background(255,255,255);
strokeWeight(3);
// draw vertical lines
int px=0;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400); px=px+40;
line(px,0, px,400);
// draw horizontal lines(省略)
抽象化の結果、プログラムはどうなったでしょうか? px=px+40 と line(px,0, px,400) を 11 回繰り返すだけとなりました。 同じプログラム片を繰り返すだけですので、全くもって冗長です。 これもなんとかしたいところです。
幸いなことに、このように考える人は私だけではなく、 プログラミング言語の設計者を始めとする多くの人がそのように感じるものです。 そのため、プログラミング言語にはこのような冗長なプログラムを 簡潔に記述するための仕組みが用意されています。
ループを使ったプログラムを書く
上のプログラムでは、同じコードが 11 回も繰り返されていました。 このような冗長なプログラムとなるのを避けるために、 Processing で使用している Java では、以下のような仕組みを用意しています。
do {
繰り返したい処理
} while(繰り返す条件);
今回の場合、繰り返したい処理は px=px+40 と line(px,0, px,400) ですので実際のプログラム片は次のようになります:
do {
line(px,0, px,400);
px=px+40;
} while(px が 400 になるまで);
さて、「px が 400 になるまで」というのはどのように記述したら良いのでしょうか? このプログラムでは、px は 0 から始まり、40 づつ値が増えていきます。 ですので「px が 400 になるまで」というのは、 「px が 400 以下である間」と読み替えることができます。 Processing のコードでは、px が 400 以下というのは、px<=400 と記述されます。 数学記号でいうところの ≤ は、いわゆる半角英数文字では表現できないため、 不等号と等号を用いて <= と表します。 ちなみに、Java においては「以下」を表す表記は <= のみであり、 =< と書くことは許されていません。
コメント:=< と書いても良いと思いますが、 Processing で使用している Java という言語の設計者はそのようには考えなかったようです。 設計した人達がそのように考えた結果ですので、そこに理由はありません。 なぜ、<= は良くて =< は駄目なのか?などとは悩んではいけません (ヒトが作りしものであり、神が作りしものではないので…)。
実際のプログラムでは以下のようになります:
// draw grid ver.3
size(400,300);
background(255,255,255);
strokeWeight(3);
// draw vertical lines by do - while loop.
int px=0;
do {
line(px,0, px,400);
px=px+40;
} while(px<=400);
// draw horizontal lines(省略)
このプログラムでは、line(0,0,0,300); も実行されているため、 厳密にはこれまでのプログラムとは異なるものです。 これまでのものと全く同じ動作に変更するためには、 int px=0; を int px=40; に変更すれば十分です。
しばらくの間、縦線のみを描く部分だけをプログラムとして掲載していました。 ループを使うと同じような処理を大量に書かなくても良いため、 実際に動作するプログラムであっても、次に示すように短いものとなります。
// draw grid ver.4
size(400,300);
background(255,255,255);
strokeWeight(3);
// draw vertical lines by do - while loop.
int px=0;
do {
line(px,0, px,400);
px=px+40;
} while(px<=400);
// draw horizontal lines
int py=0;
do {
line(0,py, 400,py);
py=py+30;
} while(py<=300);
さらなる改良
上のリストをを見ると、例えば 400 という数値など、 複数の場所に書かれているものがあります。 これはどれもキャンパスの横幅を示したものであり、 400 という数値を見ただけでは、 それがキャンパスの横幅を示すものであるとは分かりません。 また、キャンパスの数値を 400 から 500 に変更する場合、 関連する全ての数値を 400 から 500 に変更しなければならず、手間がかかります。
このようにプログラム中に埋め込まれた数値のことを、「マジックナンバー」と言います。 プログラムが正常に動作するための、いわば魔法の数値です。 一見しただけでは理屈がよく分からない魔法の数値や数字がプログラム中に散らべられていると、 プログラムの修正など保守作業の手間が著しく増えてしまいます。 そのため、ソフトウェア開発の現場においては、 これらマジックナンバーは無くすべきであることが知られています。
本章の最後として、 上で示したプログラムよりマジックナンバーをできるだけ除去してみたいと思います。 まずは、議論に上っている 400 という数値ですが、 これは既に説明したとおりキャンパスの横幅を意味しています。 新たに変数を定義しても良いのですが、 Processing ではキャンバスの横幅は width という暗黙的に定義される変数に格納されています。 なので、それを用いることにします。 同様に、300 という数値はキャンバスの高さを表すものであり、 それは height という暗黙的に定義されている変数に格納されていますので、 それを用いるように修正します。
まだ 40 と 30 という数値が残っていますが、これは格子の横幅と高さですので、
int gridWidth =40;
int gridHeight=30;
とし、これらの変数を使うようにします。
本章のまとめとして、全ての修正を反映したプログラムを以下にに示します。 出力される画像はこの章の初めの方に示したものとと全く同じで変化していません。 ちなみに、プログラムの動作を変えずにプログラムの中身、 つまりプログラムリストの書き方をより良く更新していく行為は リファクタリングと呼ばれています。
// draw grid
size(400,300);
background(255,255,255);
strokeWeight(3);
int gridWidth =40;
int gridHeight=30;
// draw vertical lines
int px=0;
do {
line(px,0, px,height);
px=px+gridWidth;
} while(px<=width);
// draw horizontal lines
int py=0;
do {
line(0,py, width,py);
py=py+gridHeight;
} while(py<=height);