yak shaving life

遠回りこそが最短の道

Vacuous Truth という概念を知っておくとプログラミングに(ちょっとだけ)役に立ちそう

TL;DR

空のコレクションに allMatch() 的な判定をするとtrueになるぞ!気をつけろ!

背景

あるリストの全ての要素がとある条件を満たすどうかを返すメソッドがあり、 allMatch(何かしらの条件) した結果をbooleanとして返すという実装になっていた。このメソッドを利用しようとした時、ふと頭に疑問が湧いた。これって、リストが空だったら結果はどうなるんだっけ…?

というわけでREPLを開いてちゃちゃっと確認してみたところ、結果はtrueであった。

# 空のリストに対して、「全ての要素が奇数である」という判定をしたら true になる…?
jshell> List<Integer> list = List.of();
list ==> []

jshell> list.stream().allMatch(v -> v % 2 != 0);
$3 ==> true

直感的にはfalseが返ってくるのかと思った(だって奇数は含まれてないし!)ので、何故こうなるのか、他の言語でも同じなのかなどを調べてみた。

プログラミング言語における実装

Java

その時使っていたのはJavaだったので、Stream APIallMatchメソッドの仕様を確認したところ、下記のように書いてあった。

If the stream is empty then true is returned and the predicate is not evaluated.

はい。普通に載ってましたね。公式ドキュメントちゃんと読めって話ですな…。でも、なぜそうなるのかは書いていない。なぜなんだろうか。

docs.oracle.com

Ruby

次にRubyArray#all?の仕様を調べてみると、arrayが空の場合については特に書いていない…と思いきや、例のところにこっそり(?)載っていた。

p [].all? {|v| v > 0 }           # => true

trueということなので、JavaallMatchと同じ挙動のようだ。しかしこれ、文章で説明書いといてくれた方が親切な気もするな。

docs.ruby-lang.org

Rust

もう一例くらい欲しいなと思ってRustの iterator::all の仕様を調べたところ、

An empty iterator returns true.

とあった。やはりtrue

doc.rust-lang.org

何故なのか

3例も調べたので、まあどのプログラミング言語でも同じ結果になると思ってよさそう1。ということは、なぜ true になるのかちゃんとした理由があるに違いない。

Vacuous Truth

どうやらこのような挙動は論理学でちゃんと定義されており、"Vacuous Truth"と呼ばれているらしい。対応する日本語はなさそうなので、Wikipediaも英語版しかない。

en.wikipedia.org

ここで書いてある例としては、「ある部屋にある全ての携帯電話は電源がオフである」という命題があった時、その部屋にそもそも携帯電話がなかった場合、この命題は真になるというもの。

ちなみに「ある部屋にある全ての携帯電話は電源がオンである」という命題もまた真になり、また「ある部屋にある全ての携帯電話は電源がオンであり電源がオフでもある」という支離滅裂な命題もまた真になる。このような命題は部屋が空であるときにしか真にならないため、"vacuously" trueと呼ばれているそうだ。へー。あ、vacuousは「空虚な」みたいな意味です。

で、結局なぜこの場合trueになるの?というところですが、「論理包含」というページ内の例に説明がありました。

ja.wikipedia.org

P が偽ならば、Q の真偽にかかわらず「P ならば Q」が真である ということらしく、直感に反している感はあるけどちゃんと説明可能とのこと。辞表の例が一番わかりやすそうな気がしたので引用します。

ある人が「この仕事が失敗したら辞表を出す」と言ったとしよう。この言葉が嘘となるのは、仕事が失敗したにもかかわらず辞表を出さなかった場合のみである。仕事が失敗して辞表を出したならば約束を守ったのであるし、仕事が成功して(失敗せず)かつ辞表を出さなかったならば、やはりその人は嘘を言わなかったことになる。仕事が成功したにもかかわらず(何か他の理由で)辞表を出した場合も、やはり嘘を言ったとはみなされないであろう。すなわち、先の宣言では仕事が成功した場合のことは何も言っていないのであるから、辞表を出そうが出すまいが本人の自由である。

なるほどなー。元の問題に立ち返ってみると、そもそも「コレクションに要素が存在するならば」という条件Pがあると考えればいいのかな。ここが偽になったらQがなんであれ真になると。

結論

ちゃんと数学の勉強をしている人なら当たり前に知っていることなのかもしれないが、Vacuous Truthという概念があり、これはプログラミングにおける「空のコレクションに対して全要素の条件マッチをするとtrueが返る」という実装に反映されている。概念レベルで一度理解してしまえばこの挙動を忘れることはないと思うので、Vacuous Truthについて理解しておくことはプログラマにとって一定の利益がある。ので理解しておきましょう。ということが言いたかったのでした。

余談

ちなみにですが、allMatchではなくanyMatchの場合、結果はfalseになります。Rubyならany?、Rustならany。いずれもfalse。何故こうなるのかはちょっとよく分かってません。

若干罠っぽい(なんで一緒じゃないの?直感的じゃない…と思いがちな)挙動ですが、allanyで結果が逆になると考えれば忘れなさそうです。


  1. 追記: よく見たら Vacuous Truth のWikipedia ページにJavaScriptPythonの例が載っていた。もちろん結果は true