Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

{}は簡易Symbolにできる

Symbolの使い方として、たとえば独自の特殊値を持ちたいと言うものがあります。

// myFind: 配列と関数を受け取り、
// 関数を満たす要素が見つかればその要素、
// 見つからなければnullを返す
const myFind = (arr, f) => {
    for (let i = 0; i < arr.length; i++) {
        if (f[arr[i]])
            return arr[i];
    }
    return null; // 見つからなかった
};
const res = myFind(arr, n => n % 2 === 0);
// ではnullを探したいときはどうする?
myFind(arr, n => n === null); // nullが見つかったのかどうかわからない

このようなケースではSymbolが有効です。たとえばNOT_FOUND = Symbol('not found')として、見つからなければこれを返すようにすれば先程の関数は完璧になります。

const NOT_FOUND = Symbol('not found');
const myFind2 = (arr, f) => {
    for (let i = 0; i < arr.length; i++) {
        if (f[arr[i]])
            return arr[i];
    }
    return NOT_FOUND;
};
const res = myFind2([1, 2, 3], n => n === null);
assert(res === NOT_FOUND);

実はこのとき、Symbolの代わりに単にNOT_FOUND = {}としても同等のことができます。なぜなら{} !== {}だからです。

もちろん細かいところは全然違っているので完全には代用できませんが、ES5でも使えるテクなので覚えておいて損はないと思います。

const NOT_FOUND = {};
const myFind3 = (arr, f) => {
    for (let i = 0; i < arr.length; i++) {
        if (f[arr[i]])
            return arr[i];
    }
    return NOT_FOUND;
};
const res = myFind3([1, 2, 3], n => n === null);
assert(res !== {});
assert(res === NOT_FOUND);

ちなみに

実は先程のNOT_FOUNDを使う手法は厳密にはまだ不十分です。なぜなら、NOT_FOUNDを探したくなったときにnullのときと同じ問題が起こるからです。

厳格にするならばmyFindの返り値は以下のようになります。

myFind(arr, n => n % 2 === 0);
// 見つかった場合 {status: 'found', value: found_value}
// 見つからなかった場合 {status: 'not found'}

とはいうものの、実際には利便性と厳密さをトレードオフしてNOT_FOUNDで実装することも多いです。

ちなみにHaskellではこういうときにMaybeが使われます。とても便利です。JavaScriptにもパターンマッチがあればMaybeでガリガリと書けるのですが…