8:関数のつづき Creative Coding p5.js

アリソンパリッシュによる

パラメータ

前に関数について述べたとき、記述した関数は、一連のステートメントを再利用できるようにすることを意図したものでした。関数には、関数を呼び出すコードから関数に値を渡せるようにすることで、コードをより抽象的にする追加機能があります。

例を示すため、前の不気味な顔に戻りましょう。


makeFace()関数は、原点を真ん中にして顔を描画します。しかし、たとえば、顔に目を2つでなく3つか4つ付けられるようなバリエーションをmakeFace()関数に加えたいとしましょう。もちろん、この機能を実装する関数をさらに記述することもできます。


”不必要な繰り返し”という感覚がうずいてくるはずです。たとえば目の数など、描きたい顔の情報を関数に与えられると、顔を作成する関数が1つで済み、その関数を呼び出すときに追加情報を与えることができます。そうなると使い勝手が非常によくなります。

JavaScriptでこれを行う最も簡単な方法は、パラメータを使用する方法です。関数定義を記述するとき、かっこの中に変数名をカンマで区切ったリストを入れることができます。関数が呼び出されると、関数呼び出しのかっこの中に入れた値は、指定した変数名として関数内で使用できるようになります。たとえば次のような関数です(役には立ちませんが)。

function drawCircle(x, y, radius) {
    ellipse(x, y, radius, radius);
}

この関数は次のように呼び出します。

drawCircle(10, 20, 30);

関数内で、値10は関数内でxとして、値20はyとして、値30はradiusとして使用できるようになります。

次のスケッチは前のものの修正版で、描画する目の数のパラメータを取るmakeFaceWithEyes()関数を使用しています(クリックすると目の数が増えます)。


演習

目の数に0や1を渡しても破綻しないようにmakeFaceWithEyes()関数を修正します。そして、笑顔の幅が指定できるように関数を修正します。

戻り値

これまで見てきた関数の中には、その目的が、ステートメントをまとめて後でそれを実行することではなく、何らかの値を計算してそれを返すことである関数がありました。その例がsin()で、特定の数値のサインを計算する関数でした。random()も画面に何も描画しない関数の例で、舞台裏で計算を行って別の値に評価することを唯一の目的とする関数でした。

こういった、何らかの値を計算してそれを返すことを行う独自の関数は自分で記述することもできます。関数が戻り値を持っている場合、その関数への呼び出しは、関数内のreturnキーワードにつづく値に評価されます。たとえば、次の関数はパラメータとして配列を取り、配列の項目の合計を返します(項目は全部数値だと仮定しています)。

function sum(t) {
  let result = 0;
  for (let i = 0; i < t.length; i++) {
    result += t[i];
  }
  return result;
}

この関数の使用例です。

const myNumbers = [5, 6, 7, 8, 9];
console.log(sum(myNumbers)); // 出力: 35

では前に見た複数の目を持つ顔のスケッチに戻りましょう。前に記述したコードの問題の1つは、目の数の多少に関係なく、サイズがいつも同じだということです。もし目のサイズをダイナミックに(動的に)変えることができたら、目の数が少ないときには、目の数が多いときよりも目を大きくできるので、機能が向上します。これを実現するには、次のような関数が記述できます。

function getEyeSize(count) {
    return 50 - (count * 5);
}

すると、この関数をmakeFaceWithEyes()内から呼び出し、そのときに、呼び出し元が希望する目の数を渡すことができます。次のスケッチはその全コードです。


値としての関数

JavaScriptの関数は、数値や配列、オブジェクトなどと同様、実際にはその種類の値です。ここまで記述してきた関数は値のように見えませんが、次の関数を作成するということは、

function sum(a, b) {
    return a + b;
}

実際には次の略記なのです。

const sum = function(a, b) {
    return a + b;
}

両方とも有効な関数宣言の方法ですが、2つめの方が、舞台裏で起きていることがより明確です。つまり、新しい変数sumを作成し、それに関数の値を代入しているのです。関数は、2つめのシンタックスで定義した場合でも、1つめのシンタックスで定義したときと同じように呼び出せます。

関数は値なので、変数に代入したり、配列やオブジェクトに入れたり、またパラメータとして別の関数に渡すことができます。その例を見るために、次のスケッチから始めましょう。ここでは単純なシェイプを画面に描画する関数を3つ定義しています。


動作自体に問題はないのですが、くくり出せる繰り返しがいくつかあります。ここでは各シェイプの移動値を手動でで設定していますが(translate()関数を使って)、理想的には、この作業を行えるループの書き方を見つけたいところです。しかし今の段階では、次のように、当該ステップでループ変数を調べる以外、この目的に適う優れた抽象化の方法をわたしたちは知りません。

for (let i = 0; i < 5; i++) {
    push();
    translate(100 + (i * 50), 200);
    if (i == 0 || i == 4) {
        drawSquare();
    }
    else if (i == 1 || i == 3) {
        drawCircle();
    }
    else if (i == 2) {
        drawTriangle();
    }
    pop();
}

これも動作はしますが、すぐ破綻します。たとえばシェイプの順番を変えたい場合、forループ内の論理を変更する必要が生じます。もっと簡単なのは、次のように、呼び出したい関数の配列を作成する方法です。


これは実に見事な方法です。callOrder配列が、呼び出す関数の順番を決めているのです(値の順番を変えたり、新しい値を最後に追加したりしてみてください)。ただし、関数の値を参照するときには、後にかっこをつけず、関数の名前を使用することに注意してください。

しかし待ってください、これは何でしょう?

callOrder[i]();

でたらめのように見えますが、実は簡単に説明できます。次の式は、

callOrder[i]

callOrder配列のi番めのインデックスに保持された*関数に評価されます。関数を*呼び出す*には、関数の値に評価される式の直後にかっこをつけます。したがって、

callOrder[i]();

は、配列のi番めのインデックスに保持された関数を呼び出すというわけです。

次のサンプルでは、3つの描画関数はそのままで、新しいrandomlyDraw()関数を加えています。この関数は、関数のリストを受け取り、関数の1つをランダムに呼び出します。


演習

パラメータとして、関数と数値を取る関数を記述します。これは、パラメータで与えられた関数を、数値のパラメータで指定された回数だけ呼び出す関数です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA