ほねもにブログ

主にプログラミングをしていて疑問に思ったことや気付いたことを書いていきます。

共通部分を一つにするのは楽しい

前回の記事では、if...else 文の中を共通化することができませんでした。しかし、関数型プログラミングの考えを取り入れることで、共通化することができました。

function move(com, i) {
    _.each(rangeOne(i, com === 0 ? 100 : 0), function(i) {
        /* 共通部分 */
    });
}

// 1ずつ増減する要素の配列を返す関数
function rangeOne(start, end) {
    return _.range(start, end, start < end ? 1 : -1);
}

前回のコードです。

function move(com, i) {
    function common() {
        // 共通部分
    }

    if (com === 0) {
        while (i < 100) {
            common();
            i += 1;
        }
    } else {
        while (i > 0) {
            common();
            i -= 1;
        }
    }
}

_.each メソッドや _.range メソッドは、Underscore.js*1 で定義されているメソッドです。Underscore.js は、JavaScript関数型プログラミングをするために作られた JavaScript ライブラリです。各メソッドの説明をしてみます。

_.each メソッド

第一引数に配列を、第二引数に関数を渡します。
このメソッドの仕事は、配列の各要素を関数に渡すことです。

_.each([1, 3, 5], function(n) { console.log(n * n) })
// => コンソール画面に「1 9 25」と表示される
_.range メソッド

全ての引数は数値です。
このメソッドの機能は、新たな配列を作成することです。

_.range(10, 15, 1) // => [10, 11, 12, 13, 14]
_.range(10, 0, -2) // => [10, 8, 6, 4, 2]
_.range(10, 5, 1) // => []

_.range メソッドの第三引数を省略すると 1 が入ります。第二引数が第一引数よりも小さいか、等しいとき、空の配列が作成されます(例の 3 番目)。

この 2 つのメソッドを使用することにより、前回成し遂げられなかった共通化ができました。とても嬉しかったです。副産物として rangeOne 関数という汎用性の高い関数もできました。これからも、共通部分を一つにすることを意識したいと思います。


今回のコードにはバグが潜んでいました。
例えば i 変数が 200 で com 変数が 0 のとき、前回のコードではループが一度も実行されないにも関わらず、今回のコードでは 100 回のループが実行されます。com 変数ではなく rangeOne 関数が増減の方向を決めているからです。以下のように訂正しました。

function move(com, i) {
    _.each(rangeMake(i, com===0?[100,1]:[0,-1]), function(i) {
        /* 共通部分 */
    });
}

function rangeMake(start, arr) {
    return funcApply(_.range, [start].concat(arr));
}

function funcApply(func, args) {
    return func.apply(null, args);
}

funcApply 関数は汎用性が高いですが、rangeMake 関数は汎用性が低いかもしれません。コードが横に長くなり過ぎないように rangeMake 関数を作ったのですが、はたしてこれは正しいのか…。そのような動機で関数を作るなら、頑張って整形したほうがいいのではないか。でも、うまいこと整形する方法がわからなかったので、rangeMake 関数という汎用性の低そうな関数を作成してしまったわけです。
とりあえず、今回はこれを完成形としておきます。ではまた。


ちょっと気になることがあったのでまたまた追記。
funcApply 関数の必要性があまりないように感じられました。

function move(com, i) {
    _.each(rangeMake(i, com===0?[100,1]:[0,-1]), function(i) {
        /* 共通部分 */
    });
}

function rangeMake(start, arr) {
    return _.range.apply(null, [start].concat(arr));
}

こちらのほうがいいかもしれません。

*1:公式サイト http://underscorejs.org/