Kotlin の emptyList.all { false } が true になるということ

こんにちは。しゅん(@MxShun)です。

突然ですが、次のKotlinコードの「any」「all」「none」がそれぞれtrue/falseのどちらを返却するか分かりますか?

val numbers = listOf(1, 2)

numbers.any { it > 0 }
numbers.all { it > 0 }
numbers.none { it > 0 }

そうですね!「true」「true」「false」となります。

val numbers = listOf(1, 2)

numbers.any { it > 0 } // true
numbers.all { it > 0 } // true
numbers.none { it > 0 } // false

https://pl.kotl.in/azGeQi-Eu

リファレンスを読んでみる

改めて、それぞれのリファレンスを確認してみました。とても直感的です。

  • any

Returns true if at least one element matches the given predicate.

kotlin-stdlib / kotlin.collections / any

  • all

Returns true if all entries match the given predicate.

kotlin-stdlib / kotlin.collections / all

  • none

Returns true if no elements match the given predicate.

kotlin-stdlib / kotlin.collections / none

「おや?」となった例示

では、次の場合はどうでしょうか?

先のコードとの違いは、numbersemptyList、つまり空のリストになっている点です。

val numbers = emptyList<Int>()

numbers.any { it > 0 }
numbers.all { it > 0 }
numbers.none { it > 0 }

結論からいうと、「false」「true」「true」となります。

val numbers = emptyList<Int>()

numbers.any { it > 0 } // false
numbers.all { it > 0 } // true
numbers.none { it > 0 } // true

https://pl.kotl.in/Z1kXyOfEF

Collections.all の違和感

私が「おや?」となったのは、「all」の返却値です。

関数名とリファレンスから、リスト内(厳密には Iterable)のすべての要素に対してPredicateが真となる場合にのみtrueを返却するものと想定していました。つまり、要素がない空のリストの場合にはfalseを返却するものと思っていました。

しかし、実際はその逆でした。

さらに極端な例を見てみます。Predicateが必ず偽となる例です。

val numbers = emptyList<Int>()

numbers.any { false } // false
numbers.all { false } // true
numbers.none { false } // ture

「おや?」となった根本原因は、「all」の空リストの扱いにありそうです。コードリーディングをしてみます。

/**
 * Returns `true` if all elements match the given [predicate].
 *
 * @sample samples.collections.Collections.Aggregates.all
 */
public inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean {
    if (this is Collection && isEmpty()) return true
    for (element in this) if (!predicate(element)) return false
    return true
}

Kotlin v1.6.20 Collections.all

要素がない空のリストの場合にはtrueを返却する仕様となっています。

私の感覚はその逆で、空リストの場合には固定でfalseが返ってくると思っていました。 同じ感覚を抱かれた方は少なくないと思います。現に、この「おや?」を独自の拡張関数を自作することで解決する方がいらっしゃるほどです。

Kotlin Collection extension function .all {} produce true for empty collection. Solution !

公式コメント

これに対し、JetBrainsのエンジニアSebastian Aignerは次のように述べています。

This might feel a bit mind-bending to think about at first, but you’ll find that this concept, which is called the vacuous truth, actually plays very well with checking conditions, and expressing logic in program code.

Advanced Kotlin Collection Functionality

つまり、Vacuous truth のコンセプトに沿っているということです。

Vacuous truth

論理学における「空虚な真」という考え方で、命題 P → Q に対し、前件 P が偽ならば後件 Q の真偽にかかわらず命題は真となるというものです。

P Q P → Q
true true true
true false false
false true true
false false true

つまり、前件である「リスト内の要素の有無」が偽の場合には、後件であるPredicateの真偽に関係なく命題(all の返却値)は真となるということですね。

まとめ

今回は、実際にKotlinを書いている中で「おや?」となったところから出発し、Kotlinのコンセプトとその根本概念である Vacuous truth に触れました。

リストの要素に対しPredicateを検査する「any」「all」「none」という各メソッドについて、最終的に私は次のように腹落ちをしました。よければ参考にしてください。

  • any:いずれかの要素に対しPredicateが真となる場合にtrueを返す。
  • all:すべての要素に対しPredicateが真となる場合にtrueを返す。ただし、要素がない場合には Predicate の真偽にかかわらず true を返す。
  • none:Predicateが真となる要素がない場合にtrueを返す。

最後に蛇足ですが、Pythonの all やSwiftの allSatisfy なども同様に Vacuous truth の概念に沿っているようです。

次回の記事もお楽しみに!

ASKUL Engineering BLOG

2021 © ASKUL Corporation. All rights reserved.