アリソンパリッシュによる
前のチュートリアルでは、p5.jsのダウンロード方法と初めてのスケッチの作成方法を見ました。このチュートリアルでは、コンピュータプログラムの基本的な概念である、式と変数、ループについて見ていきます。
目次
式
前のチュートリアルのスケッチでは、パラメータをつけた関数の呼び出し方を見ました。パラメータは全部、100や300といった数値でした。JavaScriptでは、300や150などの数値は値と呼ばれます。後のクラスでは、数値でない値のタイプも示しますが、今は、扱うのは数値だけということにしましょう。これまでのチュートリアルのスケッチではどれも、それぞれの関数はパラメータに個別の値をつけて呼び出しました。
個々の値に問題はないのですが、それは最初のうちだけです。やがですぐ、JavaScriptのどこにでも個々の値を見つけることになります。JavaScriptではまた、式と呼ばれるものを書くこともできます。式は、ほかの値を演算子と組み合わせることで、新しい値を作り出す方法です。
その働きについてはすぐお話するので、その前にサンプルを見てください。以下は4つの円を描画する基本的なスケッチです。
ここまでは単純です。しかし、これらの数値はどれも、式で置き換えることができます。最も単純な形式では、式は2つの値を持つ演算子で構成されます。1つの値は演算子の左に、もう1つは右に置かれます。たとえば下のものは上と同じスケッチですが、加算演算子( + )を使って、各円のY位置を50ピクセル変更しています。
100 + 50と書いたところが、2つの値、演算子との組み合わせという式の例です。この例の演算子は + ですが、子ども時代から馴染み深い算術演算子も全部サポートされます。ただし直観的でないものもあります。
演算 | JavaScript 演算子 |
---|---|
加算 | + |
減算 | – |
乗算 | * |
除算 | / |
以下は演算子を使って少し遊んでみたまた別のスケッチです。
乗算演算子は * で、除算演算子は / で記述します。算術や代数に慣れている方なら、/ は割り算の横棒(括線、かっせん)が傾いたものと想像できるでしょうか。割られる方は左に、割る方(除数)は右に来ます。
プログラミング初心者のみなさんには、上の例は恐ろしく奇妙で複雑に見えたかもしれません。以下は、点や記号の狂気あふれる悲惨なプールをのぞき込んでいるような気にならずに、コード行の1つを読む方法です。1行を取り出してみます。
ellipse(100, 300 + 50, 150 / 3, 150);
以下を声に出して読み上げます。
- これはellipse関数の呼び出し
- この関数では、パラメータをカンマで区切る。かっこの中には3つカンマがあるので、パラメータは4つあるはず(カンマはパラメータの間に打たれるので、パラメータより1つ少ない)。
- パラメータは100と300 + 50と150 / 3と150。
- 100と150は単純な値だか、300 + 50と150 / 3は式。JavaScriptは、スケッチが起動するとき、これらの式を値に変える。その結果、350と50になる。
- パラメータの順番は重要だが、どの位置がどんな意味を持っているのか、ドキュメンテーションを調べないと分からない。そこでドキュメンテーションを調べると、1つめのパラメータがx位置、2つめがY位置、3つめが幅、4つめが高さだということが分かる。
- したがって、この行は、X位置が100、Y位置が350、幅が50、高さが150の楕円を描画する(スケッチで見ている通りの結果)。
式を1つの値に変える過程は評価と呼ばれます。プログラムに式を記述するとつねに、JavaScriptはそれを値に変換するために評価します。
演算子の左右に半角スペースを置くかどうかはオプションです。次の2行の意味はまったく同じです。
300 + 50
300+50
プログラマーは各人が、自分にとってどのスタイルがより適切かを自分自身で決めます。
p5.jsを計算機として使う
みなさんは今、言っておられるでしょう、「待て待て。何で350じゃなくて300 + 50なんて書かなきゃならないんだ? 300 + 50が何かなんて分かってるのに」 その通りです。上記の例は特に役立つものでもありません。演算子は多くの場合、変数と使用するときに有用です。これについては後述するとして、差し当たり以降では、p5.jsを計算機として使用する方法を示したいと思います。手計算では難しい、次のような算術式があるとします。
427 * 54
p5.jsを使うとこれが計算できます。Webエディタで新しいスケッチを開始し、次のコードをsetup()関数の後の { と } の間にペーストします。
console.log(427 * 54);
かっこの中にはどんな式でも入力できます。Playボタンを押すとスケッチウィンドウは空になりますが、式の結果はブラウザのJavaScriptコンソールに表示されます。
console.log()関数は画面には何も描画しない特別な関数で、その目的は式の評価結果をコンソールに表示することです。みなさんは後に、これがプログラムのエラーを特定したり判断するときにいかに役立つかを学ぶことになるでしょう。
さらに複雑な式と演算の順序
演算子は左右どちら側にも値を取れますが、またどちら側にも別の式を取ることもできます。1つの値に評価される式に含めることのできる埋め込まれた式の数に実質的な制限はありません。
式を別の式の一部として使用するには、式をかっこで囲みます。たとえば次のようにします。
console.log(4 + (5 * 6));
この短いコードの式は4 + (5 * 6)です。これは”加算演算を実行しろ。値4を、5に6を掛けた結果に足せ”という意味で、結果は34です。
次はもっと複雑です。
console.log((18 / 3) - (3 + 2));
ここでは、3 + 2の結果を、18 / 3 の結果から引いています。JavaScriptは内部的にはこの演算を、左から右に移動して、かっこの中に式を見つけそれを評価するという、人が手計算で行うのとよく似た方法で実行します。以下はその手順です。
- (18 / 3) – (3 + 2)
- 6 – (3 + 2)
- 6 – 5
- 1
演習
式((1 + 2) * (3 + 4)) / (9 – 2)はどのように評価されると思いますか? まずは手計算してみて、次にconsole.log()を使って推測通りになるか調べてみてください。別の式に埋め込まれた式を評価するJavaScriptの規則をどう特徴付けますか?
かっこを取り除くと、JavaScriptは、式を部分で評価するときの順番を最良の方法で推測しようとします。これは、みなさんが算数で学んだのと同じPEMDAS(括弧、累乗、乗算、除算、加算、減算を意味する英単語の頭文字をつなげたアメリカ式の記憶術)のルールです。次の式を見てください。
4 + 5 * 6
JavaScriptは最初に乗算の(5 * 6) を評価し、次に加算の(4 + 30)を評価して結果の34を得ます。最初に加算を評価させたいときには、その式をかっこで囲む必要があります。結果は54( 9 * 6)になります。
(4 + 5) * 6
JavaScriptは演算を実行する順番を決める適切なルールを持っているわけですが、個人的には、かっこを使って曖昧さをなくした方がエラーは少なくなると思います。
変数
値と式が分かったので、次は、コンピュータプログラミングにおいて最も強力な概念の1つ、変数を見ていきましょう。
検索と置換を使った値の変更
前に見た4つの円のサンプルに戻ります。次のスケッチです。
問題なさそうに見えるのですが、少し大きすぎるので、円の直径を150から100に変えたいと思います。しかし150は全部で8個もあります。これを全部て作業で変えるほど人生は長くありません。そこで検索と置換機能を使って変更することにします。
これでうまくいきそうです。。。
しかし今度は小さすぎました。実は、最初から間違っていました。150ピクセルでも小さすぎたのです。再度やり直しで、円を200ピクセル幅にします。この変更を行うには、また検索と置換を使って100を200に変えればよいはずです。
function setup() {
createCanvas(400, 400);
noLoop();
}
function draw() {
background(255);
fill(255);
stroke(50);
strokeWeight(8);
ellipse(200, 200, 200, 200);
ellipse(300, 200, 200, 200);
ellipse(200, 300, 200, 200);
ellipse(300, 300, 200, 200);
}
待ってください、これは正しくない。何がどうなったのでしょう?
そうです、100は円の直径の新しい値だけではなく、XとY位置の値でもあったのです。それをうかつにも検索と置換を使って値を変えたので、XとY位置用の100も200に変わってしまったのです。
変数の助けを得る
では、この問題をどのようにして解決すればよいか考えてみてください。基本的には、複数の関数呼び出しにある同じ値を使い、後でその値を変えたいときでも、全部変えるのではなく、1カ所で済ませるようにしたいわけです。もし、JavaScriptに次のように伝える方法があれば素晴らしいでしょう。それは、”ねえ、君は関数のパラメータとして、ここに数値や式を入力させたいと思っているね。でも、その数値を探して関数呼び出しをのぞく代わりに、ほかの場所を見てくれないかなぁ。わたしは、数値を書いた‘diameter’という名前のポストイットを作った。君はコンピュータなので聞きたいことはたくさんあるだろうが、このポストイットを見ていてほしいんだ、そして、その数値を何度も何度も入力しなければならないわたしに代わって、ポストイットに書いた値を見に行ってもらいたいんだ”、という方法です。
JavaScriptにこれを行わせる方法を変数と呼びます。
もちろんこれは正確ではなく、ポストイットは存在しません。しかしプログラムには、特定の値に名前を与える特別な行を含めることができます。すると、その名前を使うことで、値自体を入力する代わりに、その値を見に行く(参照する)ことができるようになります。具体的にいうと次のようなものです。
function setup() { の上にある冒頭の1行は変数宣言です。変数宣言は次の形式を取ります。
- まずキーワードletが来て、
- 次に変数の名前が来て(名前は自分で決めます)、
- 次に等記号( = )が来て、
- その変数に”代入”したい値の式が来る
このスケッチでは、diameterという名前の変数を値200に設定したので、このスケッチ内でこの値を使用したいどの場所でも、値そのものを記述する代わりにdiameterが記述できます。すると、diameterの値を変えたい場合には、1カ所を変えるだけで済むようになります。
let diameter = 40;
function setup() {
createCanvas(400, 400);
noLoop();
}
function draw() {
background(255);
fill(255);
stroke(50);
strokeWeight(8);
ellipse(100, 100, diameter, diameter);
ellipse(300, 100, diameter, diameter);
ellipse(100, 300, diameter, diameter);
ellipse(300, 300, diameter, diameter);
}
変数はプログラムに、いくつでも含めることができます。たとえば、円の幅と高さを別々に制御したいときには、高さ用の変数と幅用の変数を2つ作ります(幅と高さを変えるので楕円になります)。
ここで使っているdiameterやellWidth、ellHeightは特別な名前ではありません。変数の名前は自分で決めるものですが、慣習的には、簡略化した分かりやすい名前が使用されます。その方が、スケッチのどんな機能が変数と関係付いているのか追跡しやすくなるからです。しかし、次のような変数名を使っていけない理由もありません。
let marriageability = 250;
let b7GxQrTto9 = 110;
function setup() {
createCanvas(400, 400);
noLoop();
}
function draw() {
background(255);
fill(255);
stroke(50);
strokeWeight(8);
ellipse(100, 100, b7GxQrTto9, marriageability);
ellipse(300, 100, b7GxQrTto9, marriageability);
ellipse(100, 300, b7GxQrTto9, marriageability);
ellipse(300, 300, b7GxQrTto9, marriageability);
}
変数は、どんな名前を付けても、正確に同じである限り(大文字小文字の一致も含めて)機能します。何をしようとしているのかを想起させてくれる変数名の方がなぜ好ましいのか、おいおい分かってくるでしょうが、必ずしもそこにとらわれる必要はありません。想像力に限界はないのです。
変数の名前付け
実生活で何かに名前を付けるときには、文字や数字、句読点のどんな組み合わせでも使用できます。JavaScriptは変数名の意味は気にしませんが、変数名を構成する個々の文字は気にします。JavaScriptの変数名の規則には次のものがあります。
- 変数名は、英字(AからZまでの大文字か小文字)で始まらなくてはならない。
- 最初の文字の後には、英字や数字、アンダースコア( _ )が使用できる。
- 変数名は1文字まで短くも、好きなだけ長くもできる(しかし控えるべき)
また、JavaScriptには予約されたキーワードがあり、これらは変数名として使用できません。その全リストはここで見ることができます。
さらに、p5.jsに組み込まれている変数や関数の名前も使用すべきではありません(たとえばellipseを変数名に使ってはいけない)。全リストはここで見ることができます。
変数にともなうエラー
変数の使用によって一番起きがちなのは、変数名を間違えた名前で入力したことによる問題でしょう。つまり、宣言で名前を付けたものの、ほかの場所で違う名前を使用した、ということです。JavaScriptはこのとき、“Uncaught ReferenceError”エラーメッセージを表示します。これは、JavaScriptが認識していない変数の名前が入力された、という意味です。下図の14という数字は、JavaScriptが問題のある場所だと考えている行番号を示しています。
変数と式
変数について理解できたでしょうか? 変数は名前を付けられた値で、元々の値の代わりにその名前が使用できます。変数を使用すると、後々の変更が1カ所で済むので作業が楽になります。
しかし、変数のパワーは、通常は値を記述する必要のあるあらゆる場面で、変数が使用できる、という点にあります。これは、変数は式の中で使用できる、ということです。たとえば、次は前の4つの円の例ですが、ここでは式の中でdiameter変数を使っているので、円のサイズを最初の円から次の円へと、少しずつ変えることが可能になります。
円の”ベース”の直径は変数で設定しています。これは、ベースの直径は変数で変更でき、変数の値を変えるだけで、同じように見える結果を得られる、ということです。
また、宣言済みの変数を、別の変数の宣言内で初期値の設定に使用することもできます。円の高さを幅の倍にしたい場合には、次のように記述できます。
ellWidthの値は変更でき、それによって、ellHeightの値に手を加えることなく、楕円の縦横比率を同じに保つことができます。
以下はここまでをまとめたスケッチです。ここでは5つの矩形を描画し、変数と式を使って、矩形に変化を加えています。draw()関数の実際のコードに手を付けずに、変数の値を変えるだけで、描画の結果を変えることができます。
変数の値を別の値に置き換えて、結果がどうなるか確認してみてください(たとえばxstepを-25のような負の数にすると、どうなるのでしょう?)。コードがどう働いているのか分からない場合は、それぞれの関数呼び出しと式の評価を手計算で追ってみてください((xpos+(xstep*4))は何に評価されるか? といった計算です。)
ノート
noStroke()は、stroke()やstrokeWeight()と同じように機能しますが、ただストロークを省略する(枠線を描画しない)ようp5.jsに伝える点が違います。noFill()関数も、シェイプの塗り色について、同様のことを行います。
変化をともなう繰り返し
もしみなさんがプラグラマーの心の中に入れたら、前の例について、次のような感想を持ったかもしれません。”繰り返しがたくさんある。本質的には同じことを何度も入力しなければならず、ろくな作業ではない。入力を手作業で行わなくてもよくすることは、コンピュータプログラミングの本質ではないのか?”と。
同じようなことを何度も入力するという冗長性を分かりやすく説明する次の簡単な例を見てみましょう。このスケッチは5つの円を横に並べて描画します。
ここでは円を5つ描画しています。円はどれも同じように見えますが、画面上のX位置が異なります。お分かりでしょうか、この方法にはいくつか問題があります。次の場合を考えてみてください。
- 描画する円の数を変えたい場合どうするか。1つや2つ加える程度なら容易だが、何百または何百万加えたいときにはどうするのか(今はばかげたことのように思えるかもしれないが、本コースの終わりには、何百万の描画は大したことではないと思えてくる)。
- 円の直径を変えたい場合にはどうするか。もちろん変数が使用できるが、必要な円の数だけ本当に変数を使用する必要があるのか? 後で変数名を変えたくなったらどうするのか。
- 以降の繰り返しで、前の矩形を並べて色を変えたように、円のほかの属性を変えたい場合はどうするのか。
すべて良い質問です。理想的には、今欲しいのは、個々のシェイプではなく特定の属性を共有する一連の多くのシェイプが欲しいのだ、とp5.jsに伝えることができる言語構造、つまり変化をともなう繰り返しの概念を伝える手段です。
ellipse()の各行は基本的には同じ値を使って同じことを行っています。より明確にするために、終わりの3つだけでなく、ellipse()の呼び出し全部が何らかの掛け算を行うように書き直してみましょう。
let ypos = 200;
let xpos = 80;
let xstep = 60;
function setup() {
createCanvas(400, 400);
noLoop();
}
function draw() {
background(255);
fill(50);
noStroke();
ellipse(xpos + (xstep * 0), ypos, 40, 40);
ellipse(xpos + (xstep * 1), ypos, 40, 40);
ellipse(xpos + (xstep * 2), ypos, 40, 40);
ellipse(xpos + (xstep * 3), ypos, 40, 40);
ellipse(xpos + (xstep * 4), ypos, 40, 40);
}
この書き直しによって、ellipse()への呼び出しは、乗算演算子の右辺の数値が違うだけで、後はまったく同じになりました。この行同士の違いを取り除き、一連の命令をもっとコンパクトに表現する方法がきっとあるはずなのです。
forループ
方法は実際に存在します。変化をともなう繰り返しを可能にする、プログラミング言語の構造に通常与えられる名前はループです(ループと呼ばれるのは、コンピュータに命令のリストをたどらせ、最初から可能な限り再開させるからです)。JavaScriptにはいくつかの異なるループ構造がありますが、最初に取り上げるのはforループと呼ばれるループです。forループは、異なる方法で使用できる柔軟な構造ですが、最初は特定のシンタックス(構文)から始めます。それは次のようなものです。
for (let i = 0; i < times; i++) {
...ここに繰り返したい事柄を記述する...
}
上記コード内のtimesは、実行したい繰り返しの回数に置き換えます。
{ と } の間には、繰り返したいステートメントを置きます。通常は少しずつ大きくなる数値を書きそうなところはどこも、変数iを置きます(変数iはforループの中でだけ使用できます)。
このままでは非常に抽象的で分かりづらいので、具体例を示します。
このようにして、前の例では5行で書いていた同じ作業がここでは2行で書けました。次は9つの円を描画するコードです。円の数が増えているので、xstepと円の直径の値を調整しています。
このサンプルに手を加え、ほかに何ができるか試してみてください。次の例では、楕円のサイズに変化を加え、背景色を変更するfill()への呼び出しを追加して、ループは12までカウントします。
forループの中にはいくつでもステートメントを置くことができます("forループの中"というのは、{ と } の間ということです)。それぞれのステートメントは繰り返し実行されます。
演習
forループを使用する例を1つ選び、forループを"展開"します。つまり、もしforループを使っていなかったら書かなければならなかったであろうすべてのステートメントを手で書き出します。
forループを深く掘り下げる
forループは根本的には、ただのカウントする(数える)方法です。これを確認する簡単な方法はconsole.log()を使うことです。次のコードを、スケッチのsetup関数内( { と } の間)に記述します。
for (let i = 0; i < 10; i++) {
console.log(i);
}
するとコンソールに次の結果が出力されます。
ご覧のように、このコードは単に0から9までの数字を出力するだけです(0からスタートし、9までを出力する10回の繰り返しです)。
forループはまた、次のコードを使用することで、もう少し高度なカウントを行うこともできます。
for (let i = start; i < end; i += step) {
...ステートメントをここに記述...
}
このコードでは、次の置き換えを行います。
- start: ループのカウントを開始したい値と置き換える
- end: ループを停止したい値と置き換える
- step: ステップごとに増やしたい数値と置き換える(いくつ刻みにするか)
たとえば、次のループは、100から300まで(300自体は含まない)、40刻みでカウントします。
for (let i = 100; i < 300; i += 40) {
console.log(i);
}
次のループは、10から50まで(50自体は含まない)のすべての偶数を表示します。
for (var i = 10; i < 50; i += 2) {
console.log(i);
}
このforループの形式を使用すると、ステップ(いくつ刻みにするか)を追跡する別の値を設定せずに、繰り返しを記述することができます。
ネストした(入れ子の)ループ
円の単純な線ではなく、グリッドを描画したい場合、どうすればよいでしょう? 方法の1つは、線のコピー&ペーストです。
しかしこれではいかにもさえないですね。少しずつ変化させ、0から別の数値までカウントするコードの繰り返しをただつづけているだけです。これはまさに、これを解決するためにforループが設計されたといううってつけの問題ではないですか? そうです、forループはこのような場合、どうやら別のforループの中に置くことができるのです。そうするには、元のループの中に別のforループを入れます。2つめ(内側)のforループを使うときには、iとは異なる別の変数をそのforループで使う必要があり、次の例ではjにしています。コード全体は次のようになります。
ノート
描画される円の数はちょうど45です(5 * 9)。なぜそうなるのか、自分自身の言葉で説明してみてください。
演習
上記サンプルを使って、ループ変数のiとjにもとづく変化をさらに追加し、塗り色やストローク、円のサイズなどを変更します。