Edited at

Kotlin Fest 2018 - Kotlin Puzzlers by CyberAgent 解答・解説


はじめに

Kotlin Fest 2018最高でしたね!

弊社CyberAgentのブースではKotlinに関するクイズを計4問出題させていただきました!

本記事では、そのクイズの解答と、なぜそうなるかの解説を行いたいと思います。


午前の部


第一問

package jp.co.cyberagent.demo

val readonly = listOf(1, 2, 3)
if (readonly is MutableList) {
readonly.add(4)
}

println(readonly)

// 実行結果は?
// a) [1, 2, 3]
// b) [1, 2, 3, 4]
// c) UnsupportedOperationException
// d) Will not compile

出展: Kotlin Puzzlers

正解は c) です。

java.util.List<E>add , remove などを含むためKotlinでは MutableList として扱われます。

listOf()java.util.Arrays$ArrayList<E> のインスタンスを返却しますが、これはもちろん java.util.List<E> を実装しているため、問題文中の is MutableListtrue となります。

java.util.Arrays$ArrayList<E>#add の実装は親クラスである java.util.AbstractList<E> にて UnsupportedOperationException が送出されるようになっているため、 c) が正解となります。

Kotlinの List<E> がimmutableなのはあくまでタイプとしての性質であって、インスタンスとしては (現時点では) mutableなものしか存在していない点に注意が必要です。


追記

コメント欄にて指摘いただきましたが、Kotlin-JS環境では正解が b) となるようです。

出題不備をお詫び申し上げます。


第二問

package jp.co.cyberagent.demo

suspend fun run() {
val context = newFixedThreadPoolContext(nThreads = 2, name = "mtPool")
List(10) {
launch(context) { delay(1000) }
}.joinAll()
}

// 実行時間は? (大体)
// a) 0 ms
// b) 5000 ms
// c) 10000 ms
// d) 1000 ms

出展: 私

正解は d) です。

launch で10個のcoroutinesを作成し、 joinAll で全ての完了を待っています。

これらのcoroutinesは2スレッドのスレッドプールを持つ context 上で実行されるため、2並列で 5000 ms かかるように思えますが、それは引っ掛けです。

ここでポイントとなるのは delay は呼び出し元スレッドをブロックしないsuspend関数であるという点です。

delay が呼び出されると呼び出し元のcoroutineは直ちに中断し、使っていたスレッドを空け渡します。

その結果、後続で控えていたcoroutineが実行され、そのcoroutineも delay が呼び出されるとスレッドを空け渡し・・・と連鎖していき、10個のcoroutinesが直ちに実行されることになります。

その後、大体 1000 ms 経つと全てのcoroutinesの実行が完了するため、正解は d) 1000 ms となります。


午後の部


第一問

package jp.co.cyberagent.demo

infix operator fun Int.plus(i: Int) = this + i + 1

println(-1 + 2)
println(-1 plus 2)
println(-1.plus(2))

// 実行結果は?
// a)1 1 1
// b) 2 2 2
// c) 1 2 -3
// d) 2 -4 -4

出展: Kotlin Puzzlers

正解は c) です。

3行目で plus を定義していますが、operator関数が被った場合は標準関数で定義されているほうが優先されるため、 -1 + 2 では自ら定義した plus は呼ばれず、そのまま 1 という答えになります。

ただし、標準関数で定義されている operator fun plusinfix 修飾子がついていないため、 -1 plus 2 の書き方では自分で定義した関数が呼び出されることになり、答えは -1 + 2 + 1 = 2 となります。

最後の -1.plus(2) についても標準関数のほうが呼び出されますが、単項演算子よりも関数呼び出しのほうが優先度が高いため -(1.plus(2)) と等価のコードになり、答えは -3 となります。


第二問

package jp.co.cyberagent.demo

suspend fun run() {
val actor = actor<Int>(capacity = 1) {
for (value in channel) delay(1000)
}
repeat(5) { actor.send(it) }
}

// 実行時間は? (大体)
// a) 0 ms
// b) 5000 ms
// c) 3000 ms
// d) 1000 ms

出展: 私

正解は c) です。

actorは Channel のラッパーのような存在ですが、 actor() が返すのは SendChannel のため、actorに値を送信することはできても、actorから値を受信することはできません。

actor() に渡すラムダ内でのみ Channel にアクセスすることができ、送信された値に応じて必要な処理を行うことができます。

actor() によるインスタンスの生成にはほとんど時間がかからないと考えてよいので、この問題ではactorに 0, 1, 2, 3, 4 の5つの数字を送り終えるまでの時間が答えになります。

まず、最初の0の send はactorの channel にまだ何も値が入っていないためそのまま成功します。

actorは channel に入った0を直ちに receive して delay(1000) を実行します。

次の1の send は、actorの channel に何も値が入っていないため、これまた成功します。

現在actorは0を receive したときの delay 中なので、 1 はまだactorによって receive されず、 channel に積まれたままになります。

そのため、2の sendchannel が空くまで中断されます。

1000 ms ほど経つと、0を receive したときの delay が完了し、actorは channel から1を receive します。

ここで channel が空になったため、中断していた2の send が完了し、次の3の sendchannel に空きがないので中断されます。

この流れが繰り返され、さらに 1000 ms 後に3の send が完了し、さらに 1000ms 後に4の send が完了することになるため、答えは c) 3000 ms となります。


終わりに

Kotlinかわいい!