はじめに
Array.some
とArray.every
、どちらも配列においてとても便利なメソッドですね。
自分も幾度となく使っていてお世話になっています。
そんな頻出のメソッドではありますが、空の配列だった時の挙動がちょっと意外だったので備忘録も兼ねてまとめてみました。
前提:Array.some
とArray.every
って何?
日々JavaScriptと戦っている方なら解説不要かと思いますが、一応記述しておきます。
Array.some(callbackFn)
-
配列の要素それぞれで
callbackFn
を実行し、true
を返す要素が一つでもあればtrue
を返し、そうでなければfalse
を返す。
砕いて言うと、配列の中で条件を満たすものが一つでもあればOKとする ということ。 Array.every(callbackFn)
-
配列の要素それぞれで
callbackFn
を実行し、全ての要素でtrue
が返されればtrue
を返し、そうでなければfalse
を返す。
砕いて言うと、配列の全てで条件を満たせばOKとする ということ。
[1, 3, 10].some((x) => { return x > 5; });
// true
[1, 3, 5].some((x) => { return x > 5; });
// false
[6, 8, 10].every((x) => { return x > 5; });
// true
[6, 8, 1].every((x) => { return x > 5; });
// false
結果
というわけでタイトルのお話に戻りますが、結果は下記のようになります。
[].some((x) => { return x > 5; });
// false
[].every((x) => { return x > 5; });
// true
...あれ?
Array.some
は分かります。空配列ならtrue
を返す要素が一つもないので。
Array.every
はなんでそうなったんだ??おお??
君は空のカゴを指して「カゴの中に入っている物は全部りんごですか?」って聞かれたら「はい!そうです!」って言うのか???
これは納得できない。こんなこと言ったら間違いなく周りから眉をひそめられます。
もう某魔法使いの物語の最後の戦いでルーピ〇とト〇クスが死んだ時くらいに納得できない。あれはなぜ?
実際なぜこうなるのか?
そういう風に定義したからです。
...というのは無いと思うので、ちゃんと調べてみました。
とりあえずMDNでも見てみましょう。
every は数学における「∀ (すべての / for all)」記号と同様のふるまいをします。特に、空の配列に対しては true を返します。(空集合のすべての要素が与えられた任意の条件を満たすことは空虚に真です。)
(Array.prototype.every()#解説 MDN より)
なるほど!...か?
となりましたが、「空虚な真」という言葉を調べてみると、要は命題の前件が偽なら後件の真偽に関わらず命題は真とするということだそうです。
つまり、Array.every
で空の配列を渡された時点で命題の前件は配列が空=偽になり、callbackFn
による評価は無関係に命題は空虚な真となる、つまりtrue
が返されるということですね。ほ~
よし、これからは空のカゴを指して「カゴの中に入っている物は全部りんごですか?」って聞かれても「はい!そうです!」って元気に答えるぞ!
空集合について
この記事を書く時にこの記事を見つけたのですが、空の配列を空集合(∅)と捉えることでtrue
が返されるべき、という結論に納得することができました。集合についてはあらゆる任意の集合は空集合を部分集合に含んでいるという基礎が記憶がわずかにある程度だったので、流石にこの部分を自分で記述するのはもっと詳しい人が既に解説してくれていることもあり気が引けました。。。
ただ、このコメントにあった X ⊂ A は成り立っても X ⊂ B は成り立たない理由は面白かったです。ありがとうございました。
そもそもの話
前半でArray.every
について配列の全てで条件を満たせばOKとすると書きましたが、この表現自体がそもそも違っていた様子でした。
いや間違ってないのかもしれないけど、自然言語としては導かれた結果からすると明らかにおかしいですね。
書き換えるなら配列の中で条件を満たさないものが一つも無ければOKとするという表現になるでしょう。これなら空配列でtrue
が返されても納得です。
...ということなんですが、そもそもMDNにはちゃんと記載されていました。
callbackFn 関数がいずれかの要素で偽値を返した場合は、すぐに false を返します。それ以外は true です。
(Array.prototype.every()#返値 MDN より)
everyというメソッド名からは直感的に連想できないような表現になりますが、挙動から考えるとこれなら間違えないですね。
途中の「カゴの中に入っている物は全部りんごですか?」という質問も、Array.every
さんは実は「カゴの中に入っている物にりんご以外は含まれていないですか?」と聞かれていたということでした。
言葉で覚えるのは人間なので当然ですが、ちょっとした表現の違いで誤解が出るな~と思うと難しいですね。
余談
Array.every
と同様のメソッドは他の言語でもありますが、Pythonのall(iterable)
やRustのIterator::all
、JavaのarrayList.stream().allMatch
も同様に空配列だと(True)true
を返すようです。
プログラミング言語の中ではこれが一般的なんですねー
最後に
ということで、メソッドの解釈や覚え方には気を付けようという教訓でした。あとは空配列のようなイレギュラーケースについては特に確認すべきということも。
また、思いっきりすっ飛ばしましたがArray.some
がなぜ空配列だとfalse
なのか?もちゃんと理由があるようなのですが、今回はここまでとします。
ちょっとした事象ですが、調べると奥が深くてまだまだ分からないことだらけだと痛感する出来事でした。これからも頑張って少しずつ理解していきます。