JavaScript Diary

ブロック変数と中括弧ブロックの勧め [ 2003/03/31 ]

最初に書いておきますが,この「ブロック変数」という言葉が正式な名称かは分かりません ^^;

変数は大きく分けて3つの種類があります.よく聞くのはグローバル変数(広域変数),そしてローカル変数(局所変数)ですが,
これに加え,SunのJavaコンパイラやBorland C++ Builderでは,ブロック変数というものが存在します.

ブロック変数とは例えば以下のようなものです(Java).

[1]

for (int i = 0, n = array.length; i < n; i++) {
    ...
}

この infor文のなかでしか使えない(見えない),言うならば超局所変数です.
これはよく見る例ですが,[2]のような書き方をする人はいますか?

[2]

void f() {
    ...
    {
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    ...
}

この tmp はブロック変数です.中括弧で囲まれる範囲でしか見えていません.また,[3]のように書いてもコンパイルは通ります.

[3]

void f() {
    ...
    {
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    ...
    {
        int tmp = b[i];
        b[i] = b[j];
        b[j] = tmp;
    }
    ...
}

こう書くことの利点ですが,先ず名前空間を汚しません.[4]と比較すれば分かると思います.

[4]

void f() {
    ...
    int tmpA = a[i];
    a[i] = a[j];
    a[j] = tmpA;
    ...
    int tmpB = b[i];
    b[i] = b[j];
    b[j] = tmpB;
    ...
}

んじゃ,ローカル変数としてtmpを1つ宣言しておけばいいじゃん,と思われるかも知れませんが([5])

[5]

void f() {
    int tmp;
    ...
    tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
    ...
    tmp = b[i];
    b[i] = b[j];
    b[j] = tmp;
    ...
}

他人が見て,分かりやすいプログラム,保守性の高いプログラムを書くためには,
処理速度やメモリを気にしない限り「変数は使う直前で宣言する」というコーディングスタイルに従う方が吉です.
また,[5]の場合,int型のtmpdouble型のtmpが存在した場合問題になります([6]).

[6]

void f() {
    int iTmp;
    double dTmp;
    ...
    iTmp = intArray[i];
    intArray[i] = intArray[j];
    intArray[j] = iTmp;
    ...
    dTmp = doubleArray[i];
    doubleArray[i] = doubleArray[j];
    doubleArray[j] = dTmp;
    ...
}

[6]をブロック変数を使って書くと[7]のようになります.

[7]

void f() {
    ...
    {
        int tmp = intArray[i];
        intArray[i] = intArray[j];
        intArray[j] = tmp;
    }
    ...
    {
        double tmp = doubleArray[i];
        doubleArray[i] = doubleArray[j];
        doubleArray[j] = tmp;
    }
    ...
}

ところで「全ての変数名には出来るだけ意味のある名前を付ける」なんていうスタイルもあるようですが,
swap に使う tmp 又は temp という変数名だけは譲れません.僕的に,余計に分かり難くなりそうですし.

さて,もう1つの利点ですが「見た目,分かりやすいプログラム」になります.
例えば[7]のプログラムですが,中括弧で囲まれるブロックが「意味のあるブロック」だと考えれば
(というか中括弧ブロックを意味のあるものと暗黙のルールを決めておくと良い),
一つ目のブロックはintArrayの要素を入れ替える,二つ目のブロックはdoubleArrayの要素を入れ替えるとすぐに分かります.
上下の改行もなしで[6]のようなプログラムを書けば,確実に読みにくいものとなるはずです.

また,これらの例はブロックで「swap処理を行う」というものでしたが,[8]のような意味でのブロックもあります.

[8]

void f() {
    ...
    double[] u, v, w;
    {
        final double NORM = 1.0;

        u = new double[3];
        u[0] = NORM;
        u[1] = 0.0;
        u[2] = 0.0;

        v = new double[3];
        v[0] = 0.0;
        v[1] = NORM;
        v[2] = 0.0;

        w = new double[3];
        w[0] = 0.0;
        w[1] = 0.0;
        w[2] = NORM;
    }
    ...
}

[8]の中括弧ブロックは「u, v, w の初期化」という意味のブロックです.
[9], [10]のように書くよりも総合点では高いはずです.

[9]

void f() {
    ...
    final double NORM_UVW = 1.0;

    double[] u = new double[3];
    u[0] = NORM_UVW;
    u[1] = 0.0;
    u[2] = 0.0;

    double[] v = new double[3];
    v[0] = 0.0;
    v[1] = NORM_UVW;
    v[2] = 0.0;

    double[] w = new double[3];
    w[0] = 0.0;
    w[1] = 0.0;
    w[2] = NORM_UVW;
    ...
}
[10]

void f() {
    ...
    double[] u = new double[3];
    double[] v = new double[3];
    double[] w = new double[3];

    initUVW(u, v, w);
    ...
}

void initUVW(double[] u, double[] v, double[] w) {
    final double NORM = 1.0;

    u[0] = NORM;
    u[1] = 0.0;
    u[2] = 0.0;

    v[0] = 0.0;
    v[1] = NORM;
    v[2] = 0.0;

    w[0] = 0.0;
    w[1] = 0.0;
    w[2] = NORM;
}

さて,ここからはJavaScriptの話になりますが,JavaScriptではブロック変数を実現することは不可能です.
もともとグローバル変数とローカル変数しか存在し得ません.下手すれば(考え方に依る),ローカル変数のみです.
というのも以下のように書いても全てローカル変数扱いになります.

function f() {
    ...
    for (var i = 0, n = 10; i < n; i++) {
        
    }

    alert(i); // エラーにならず10と表示される
    ...
}
function f() {
    ...
    var u, v, w;
    {
        var norm = 1.0;
        ...
    }

    alert(norm); // 1.0と表示される
    ...
}

しかし,JavaScriptではローカル変数は何度宣言してもエラーにはなりませんので,割り切ってしまえば↓のように書けます.

function f() {
    ...
    {
        var tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    ...
    {
        var tmp = b[i];
        b[i] = b[j];
        b[j] = tmp;
    }
    ...
}

tmpはローカル変数扱いになりますから,ブロックを抜けたあとtmpをアラートで表示するとそれなりの値が得られます.

JavaScriptでは不可能かというと一応,ブロック変数なことは出来ます.

function f() {
    ...
    function() {
        var tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }();
    ...
    function() {
        var tmp = b[i];
        b[i] = b[j];
        b[j] = tmp;
    }();
    ...
}

関数の中で無名関数を定義し,ローカル変数をブロック変数とした例ですが,んー,ちょっと苦しいですね ^^;

僕がよく使うのは先ほどの初期化の意味[8]での使い方です.

// http://www.foo.com/~ID/boo.html などのURIから,ID部分を取り出し,userNameに格納
var userName;
{
    var pathName = location.pathname;
    var index0 = pathName.indexOf("/~");
    if (index0 == 0) {
        var pathNameDash = pathName.substring(index0 + 2);
        var index1 = pathNameDash.indexOf('/');
        if (index1 == -1) {
            userName = pathNameDash;
        } else {
            userName = pathNameDash.substring(0, index1);
        }
    } else {
        userName = "";
    }
}

これら↑のブロックで宣言されている変数(pathName, index0, pathNameDash, index1)は
このままでは全てグローバル変数(ローカル変数)になりますが,
これを回避するために同じように無名関数を使い以下のように書けます.

var userName = function() {
    var pathName = location.pathname;
    var index0 = pathName.indexOf("/~");
    if (index0 == 0) {
        var pathNameDash = pathName.substring(index0 + 2);
        var index1 = pathNameDash.indexOf('/');
        if (index1 == -1) {
            return pathNameDash;
        } else {
            return pathNameDash.substring(0, index1);
        }
    } else {
        return "";
    }
}();

↓のように書くのとは意味が違うことに注意して下さい.要はメソッドにするまでもない,というような処理ですね.

function getUserName() {
    ...
}

var userName = getUserName();

余談になりますが,この中括弧ブロックはツリー構造やGUIコンポーネントの初期化などにも有用かと思います.

private void initMenuBar() {
    this.menuBar = new MenuBar(...);
    {
        this.menuFile = new Menu(...);
        {
            this.itemOpen = new MenuItem(...);
            // ...(itemOpenの初期化.リスナーの追加など.)
            this.menuFile.add(this.itemOpen);

            this.itemPrint = new MenuItem(...);
            // ...(itemPrintの初期化.リスナーの追加など.)
            this.menuFile.add(this.itemPrint);

            ...
        }
        this.menuBar.add(this.menuFile);

        this.menuFoo = new Menu(...);
        {
            this.menuBoo = new Menu();
            {
                this.itemBoo0 = new MenuItem(...);
                // ...
                this.menuBoo.add(itemBoo0);

                this.itemBoo1 = new MenuItem(...);
                // ...
                this.menuBoo.add(itemBoo1);

                this.itemBoo2 = new MenuItem(...);
                // ...
                this.menuBoo.add(itemBoo2);
            }
            this.menuFoo.add(this.menuFoo);

            ...
        }
        this.menuBar.add(this.menuFoo);

        ...
    }
}

インデントがあることで,ツリーの構造が分かりやすくなったのではないでしょうか(あまりに複雑な場合は項目ごとにメソッドに分けるのが定石かと思いますが).

また,このブロック変数はテストケース作成時にも重宝します.例えば以下のようなものです.

public void testInitialize(){
    Point p0 = new Point();
    assertEquals(p0.x, 0.0, EPS);
    assertEquals(p0.y, 0.0, EPS);

    Point p1 = new Point(1.0, 2.0);
    assertEquals(p1.x, 1.0, EPS);
    assertEquals(p1.y, 2.0, EPS);

    Point p2 = new Point(-2.0, 1.0);
    assertEquals(p2.x, -2.0, EPS);
    assertEquals(p2.y, 1.0, EPS);

    ...
}

変数に添え字を付けて同じようなテストを作成する例ですが,
添え字に変更を加えなくてはいけなくなったりした場合,全ての変数を書き換えなくてはいけない必要が出てくるかも知れません.
このような場合,テスト1つ1つをブロックにしておくとです↓

public void testInitialize(){
    {
        Point p = new Point();
        assertEquals(p.x, 0.0, EPS);
        assertEquals(p.y, 0.0, EPS);
    }
    {
        Point p = new Point(1.0, 2.0);
        assertEquals(p.x, 1.0, EPS);
        assertEquals(p.y, 2.0, EPS);
    }
    {
        Point p = new Point(-2.0, 1.0);
        assertEquals(p.x, -2.0, EPS);
        assertEquals(p.y, 1.0, EPS);
    }
    {
        ...
    }
    ...
}