こんにちは。しゅん(@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
リファレンスを読んでみる
改めて、それぞれのリファレンスを確認してみました。とても直感的です。
- 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
「おや?」となった例示
では、次の場合はどうでしょうか?
先のコードとの違いは、numbers が emptyList、つまり空のリストになっている点です。
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
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 の概念に沿っているようです。
次回の記事もお楽しみに!