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.