Skip to content

かんたんプログラミング #5 関数をもっと呼び出す

前回、ゲームを作ってみます、と言ったのですが、「shi3z式ゲームプログラミング」が始まってしまったのでこの連載では引き続き粛々とプログラミングの楽しさを紹介していきたいと思います。

さて、今回のテーマは「再帰(さいき)」です。

再帰・・・再び帰ってくる。まるで宇宙探査機はやぶさのようです。

今回のテーマを学ぶとこんな図形を描けるようになりますよ

さあやってみましょう

(続きは以下のリンクから)

前々回、関数というものを学びました。

ところで関数の中からさらに関数を呼んでみるとどうなるでしょう?

・・・というのは説明するまでもないかもしれませんね。

なにしろいままでonLoadという主たる処理を書いていた関数から、さらに自分で定義したcircleやrandという関数を呼び出して使っていた訳です。

そう。もちろん、関数の中から関数を呼び出すことも簡単にできるんです。

では・・・関数から関数自身を呼び出したらどうなるでしょうか・・・

実は・・・大変なことになります。
下のコードはサンプルですが、決して実行しないで下さい。ブラウザが壊れることがあります。


function onLoad(){
  onLoad(); // こうしたらどうなる?
}


Safariなどの賢いブラウザは、こんなふうに警告を発してくれます。
この警告はどういう意味でしょう?

maximum call stack size exceeded(最大コールスタックサイズを超えました)」

実は、ここに関数呼び出しの秘密があります。
関数は自由に呼び出せるが、無限に呼び出せる訳ではないのです。

つまり「最大」の呼び出し数が決まっているということで、それが「コールスタックサイズ」なのです。

onLoad関数のなかで無条件にonLoad関数を呼ぶと、それは無限に新しいonLoad関数を呼び出してしまいます。

すると、最大コールスタックサイズがどれだけ大きく設定されていたとしても、確実に最大コールスタックサイズを超えてしまうのです。

通常は十分なだけのコールスタックサイズが設定されているのですが、どれだけ大きなコールスタックサイズを確保したとしても、無限にあるわけではないので必ずエラーになります。

こんな危険な機能・・・つまり自分自身を呼び出す関数という機能はなぜ実装されているのでしょうか?

実は関数が自分自身を呼び出す機能、というのは、上手く使えば非常に有効な道具になってくれるのです。それが「再帰」というテクニックです。

以下のコードを見て下さい。


function onLoad(){
    var proc = function(t){
        if(t<1) // tが1未満になったら再帰をやめる
            return;
        for(var i=0;i<360;i++){ //円を描く
            move(t);
            turn(1);
        }
        proc(t*0.8); //tに0.8を掛けて再び円を描く
    };
    penDown();
    proc(3);
    logo.penUp();
}

実行すると下のようになります。

今度はブラウザもおかしくならずにうまく動作しました。

これはなにが起きているのでしょう?
ご覧のようにprocが何回も呼び出されていますね。
このプログラムの動きを知るには、alertを使うと簡単です。

以下のように書き換えて下さい


function onLoad(){
    var proc = function(t){
        alert("t="+t);
        if(t<1) // tが1未満になったら再帰をやめる
            return;
        for(var i=0;i<360;i++){ //円を描く
            move(t);
            turn(1);
        }
        proc(t*0.8); //tに0.8を掛けて再び円を描く
    };
    penDown();
    proc(3);  //最初のtの値に3を代入する
    logo.penUp();
}

すると、procが呼び出される度にalertでtの値が表示されます。
最初はtの値は3です。

次は「t=2.4000000000000004」となりますが、ほぼ2.4、そして1.92、1.536、1.2288、0.98304となってtが1未満になったので再帰は終わります。

また、右側の画像にも注目してください。
再帰が繰り返されるほどより半径の小さい円が描かれていきます。

このように、自分自身を呼び出しながらも、呼び出し回数にわざと制限を設けて呼び出すのが再帰です。

使いこなすのが難しいと思いましたか?
実際、再帰は慣れるまで非常に理解するのが大変な考え方のひとつです。
逆に再帰を使いこなせるようになれば、プログラマとして一人前になった、と言えるかもしれません。

最後に、再帰を使って美しい木のグラフィックを出す方法をご紹介して今回は終わりにしたいと思います。


function onLoad(){
        a=20;
        b=0.8;
    var proc = function(t){
        if(t<10){ // tの値が10未満まで小さくなったら再帰をやめる
            move(t*b);  //進んで
            move(-t*b); //戻る
            return;
            }
        turn(-a*b);
        move(t*b);
        proc(t*b);      //ここで自分自身を呼び出す(再帰呼び出し)
        move(-t*b);
        turn(a*b);
        turn(a);
        move(t*b);
        proc(t*b);    //ここで自分自身を呼び出す(再帰呼び出し)
        move(-t*b);
        turn(-a);
    };
    penUp();
    turn(180);
    move(-100);
    penDown();
    proc(80);
    logo.penUp();
}

なにが起きているかわかりましたか?
こういうプログラムを理解するコツは、適当に数字を変えてみたり、moveやturnの順番を変えてみたりすることです。

なあに、よほどのことが起きなければ驚くほどのことはありません。
ただし相手にしているのは再帰です。

無限の再帰に気をつけて。
ではまた次回お会いしましょう

文・alan

このエントリーをはてなブックマークに追加
はてなブックマーク - かんたんプログラミング #5 関数をもっと呼び出す
Post to Google Buzz
Share on GREE

Related posts:

  1. かんたんプログラミング #2:亀で学ぶ楽しいループの仕組み
  2. かんたんプログラミング #1:亀の子グラフィック
  3. かんたんプログラミング #4 コンピュータが判断する?
  4. かんたんプログラミング #3 関数ってどんな数?
  5. shi3z式ゲームプログラミング入門 #1 72時間でゲームプログラマになる方法

Facebook comments:

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*