Kotlin 赤べこ本メモ

[Kotlinスタートブック](http://www.ric.co.jp/book/contents/book_1039.html) (通称？赤べこ本) を読んでて気になったところメモ。

## P.54 val で宣言した MutableMap と plus()、plusAssign()

```kotlin:MutableMapサンプル
package sample

fun main() {
    val numMap = mutableMapOf("one" to 1, "two" to 2)

    // 実体としてはオーバーロードされた MutableMap.plusAssign(Pair) が呼び出される
    numMap += "three" to 3
}
```

`+=` 演算子がオーバーロードされてるんだなー、これ `val` で宣言してるけど `numMap = numMap + "three" to 3` はどうなん？と思ったら案の定駄目 (コンパイルエラー) だった。

理屈は分かるけど `A += B` と `A = A + B` が等価じゃないのは奇妙な感じ。

それはそれとして `MutableMap.put(Pair)` が定義されてないのは何故なんだろう。
`numMap.put("three" to 3)` と書きたかった。


## P.73 可変長引数の位置

可変長引数は 1関数につき 1つまでだよ、という説明はあるけど引数の位置についてはどうなんだろ？
Java だとたしか引数リストの最後の引数にしか指定できないし。

という点が気になったので公式の説明を読む。

[Functions - Variable number of arguments (Varargs)](https://kotlinlang.org/docs/reference/functions.html#variable-number-of-arguments-varargs)

> A parameter of a function (normally the last one) may be marked with vararg modifier:
> (中略)
> Only one parameter may be marked as vararg. If a vararg parameter is not the last one in the list, values for the following parameters can be passed using the named argument syntax, or, if the parameter has a function type, by passing a lambda outside parentheses.

- 大抵の場合、`vararg` は引数リストの最後の引数に付ける
- 最後の引数以外に `vararg` を付けた場合、それより後ろの引数については名前付き引数で呼び出す必要がある

```kotlin:varargな引数の後ろに引数がある
package sample

fun main() {
    fun sum(vararg ints: Int, s: String): Int {
        var sum = 0
        for (i in ints) {
            sum += i
        }
        return sum
    }
    // 仮引数名 s の明示が必要
    println(sum(1, 2, 3, s = "HOGE"))
}
```

- `vararg` な引数の後ろに関数型の引数が続く場合は、呼び出し時に関数の外側 (閉じカッコの後ろ) にラムダを指定する形で引数を指定できる (ただし 1個だけ)

```kotlin
package sample

fun main() {
    fun f1(vararg ints: Int, f2: (Int) -> Int): List<Int> {
        return ints.map(f2)
    }
    // 関数の外側にラムダを指定
    println(f1(1, 2, 3) { -it })     // -> [-1, -2, -3]
    println(f1(1, 2, 3) { it * it }) // -> [1, 4, 9]
}
```

…という感じで理解した。

**とはいえ最後の引数以外に `vararg` を付けたりすることはそうそうないはずなので、上記の仕様は特に覚えなくてもいいと思う。**
可変長引数はオプションのような引数 [^1] なので、余程のことがない限りは引数リストの最後に位置するよう関数を定義するはず。

[^1]: 要素数が 0個の場合は見た目上省略されるので。


## P.74 再帰の末尾呼び出し最適化 vs. 繰り返し処理

P.74-76 は再帰呼び出しと末尾呼び出し最適化に関する内容。
末尾呼び出し最適化をすると確かにスタックオーバーフローは起きなくなったけど処理はすげー遅い。
`(1L..123456).toList()` の合計を求めるのに数十秒かかる (マシンが人権不足) 。

```kotlin:再帰版
package sample

fun main() {
    tailrec fun sum(numbers: List<Long>, accumulator: Long = 0): Long =
        if (numbers.isEmpty()) accumulator
        else sum(numbers.drop(1), accumulator + numbers.first())
    println(sum((1L..123456).toList()))
}
```

これ普通に繰り返し処理でやってもこんなかかるの？と気になったので繰り返し処理に置き換えて試した。
for文書いてもいいけどもっといい感じのがあったはず… `Iterable.reduce()` だ。

```kotlin:繰り返し版
package sample

fun main() {
    fun sum(numbers: List<Long>): Long =
        numbers.reduce { acc, n -> acc + n }
    println(sum((1L..123456).toList()))
}
```

実際に計測はしなかったけど繰り返し版の方は実行して 3秒もかからず結果が出た。
末尾呼び出し最適化したらよしなに繰り返し処理に展開してくれるもんだと思ってたけど、体感できるほどの差が出るもんなんだなあ。

と思ったけど再帰版は `numbers.drop(1)` で都度新しい `List` を作ってるのでそれが遅い原因な気もする。


## P.82 ローカル関数の関数参照を `println()` しようとしてエラー

赤べこ本では `main()` の外で `square()` を定義しているが、`main()` 内で定義して `println(::square)` を実行しようとしたらエラーになった。

```kotlin:ローカル関数として定義
package sample

fun main() {
    fun square(i: Int): Int = i * i
    println(::square)
}
```

```bash:からの実行時エラー
Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: 
Introspecting local functions, lambdas, anonymous functions and local variables is not yet fully supported in Kotlin reflection
    :
    at kotlin.jvm.internal.FunctionReference.toString(FunctionReference.java:98)
    at java.lang.String.valueOf(String.java:2994)
    at java.io.PrintStream.println(PrintStream.java:821)
    :
```

ローカル関数の関数参照って未サポートで取得できないのかー。
と思ってたけど、本を読み進めてたら P.84 で普通に出てきた。
勘違いだった。

エラーは `println()` 内で発生してるので関数参照は取得できてる (そこから `toString()` を呼び出そうとしてエラーになってる) 。
スタックトレースちゃんと見よう。


## P.124 ローカルでのメソッドとプロパティの追加

関数ローカルで `String` クラスのメソッドとプロパティを追加してみようと思ったが、メソッドは追加できたがプロパティはできなかった。

```kotlin:ローカルでメソッドとプロパティを追加したい
package sample

fun main() {
    val s = "I like Kotlin."

    // ローカルでメソッドの追加：OK
    fun String.countWords(): Int =
        this.split("""\s+""".toRegex()).size
    println(s.countWords())

    // ローカルでプロパティの追加：NG
    // コンパイラ様が Local extension properties are not allowed. とお怒りになる。
    val String.wordsCount: Int
        get() = split("""\s+""".toRegex()).size
    println(s.wordsCount)
}
```

P.160 に出てきた型パラメータのプロパティも同様に追加できなかった。

「ゲッター/セッターの見た目がちょっと違うやつ」という認識なのでメソッドの追加と同じ扱いのような気もしたが、そうでもないらしい。
ちゃんと調べたいが色々深そうなので一旦保留しておく。


## P.147 Kotlin のインタフェースはメンバに実装を持てる

> インタフェースの提供するメンバは、実装を持つことができます

Java (Java 8 以降) のインタフェースみたいにデフォルト実装を持つことができるとのこと。
そのためにキーワード (Java だと `default`) は必要ないようだ。


## P.161 複数の上限境界を指定した場合、型引数は指定した全ての型を含む必要がある

```kotlin:P.161のリスト11.7
interface Hoge
interface Fuga
interface Piyo: Hoge, Fuga

class Baz<T> where T : Hoge, T : Fuga

fun main(args: Array<String>) {
    Baz<Piyo>() // OK
}
```

試しに上記の `Piyo` の定義から `Fuga` を削除し `interface  Piyo: Hoge` にしたところ、コンパイルエラーになった。

```kotlin:PiyoがFugaを継承しない
interface Hoge
interface Fuga
interface Piyo: Hoge // Fuga を継承しない

class Baz<T> where T : Hoge, T : Fuga

fun main(args: Array<String>) {
    Baz<Piyo>() // コンパイルエラー
}
```

仮に「 `where` で指定されたいずれかの型であればよい」という仕様の場合、`Baz` 内の処理で `Piyo` のインスタンスに対して `is Hoge` や `is Fuga` をすることになる。それは嬉しくない。


## P.162 変位ヨクワカラナイ

共変とか反変とか何なの。分からんような忘れたような。
というわけで [なぎせゆうきさんの Java Generics Hell Advent Calendar 2017](https://adventar.org/calendars/2751) を読み直したりした [^2] 。
[^2]: タイトルの通り Kotlin ではなく Java のジェネリクスについてのアドカレ。文法の違いはあれど本質的な部分は同じだと思う。

下記のエントリで少し理解できた (思い出した) 気がする。

- [4日目 配列と変性](https://nagise.hatenablog.jp/entry/20171204/1512395207)
- [13日目 共変ワイルドカード](https://nagise.hatenablog.jp/entry/20171213/1513172356)
- [14日目 反変ワイルドカード](https://nagise.hatenablog.jp/entry/20171214/1513260215)

言葉そのものについては Wikipedia の記事が参考になった。
ピンと来たわけではないけど、どこ由来の言葉なのかは分かった。

- [https://ja.wikipedia.org/wiki/共変性と反変性_(計算機科学)](https://ja.wikipedia.org/wiki/%E5%85%B1%E5%A4%89%E6%80%A7%E3%81%A8%E5%8F%8D%E5%A4%89%E6%80%A7_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%A6))


## P.188 `mod` 演算子は非推奨

> `MyInt`を剰余演算に対応させるため、拡張関数`mod`を追加してみましょう。

`mod` は Kotlin 1.1 で非推奨になったので代わりに `rem` 演算子をオーバーロードした。

- [What's New in Kotlin 1.1 - rem operator](https://kotlinlang.org/docs/reference/whatsnew11.html#rem-operator)


## P.194 `Pair` のクラス定義に `componentN()` がない。と思ったらデータクラスだった

分解宣言の例で `Pair` が紹介されていたのでそのクラス定義を覗いてみたが `componentN()` なメソッドが見当たらない。

なぜ？と思いつつ定義をよく見ると `public data class Pair ...` となってた。
`Pair` はデータクラスで `componentN()` は自動生成されるのでクラス定義には現れてこなかった、というのが真相だった。

なお、赤べこ本でデータクラスが説明されるのは分解宣言の次の節 (P.196-198) なので、`Pair` の定義を覗くのはそちらを見てからにすると戸惑いがない。


## P.206 シールドクラスのサブクラスを非公開にすると `else` のない `when` 式でコンパイルエラーになる

そりゃそうだろうと思うが、気になったので試した。

```kotlin:Sealed.kt
package ch13adt

sealed class Sealed

// こっちは公開
class SealedSub1: Sealed()

// こっちは非公開
private class SealedSub2: Sealed()
```

上記のように、シールドクラスのサブクラスについて一部は公開するが一部は非公開にする。
その状態で `else` のない `when` 式を書くとコンパイルエラーになる。

```kotlin:Hoge.kt(分岐が足りない場合)
package ch13adt

fun hoge(o: Sealed) {
    // コンパイルエラー：分岐が足りない
    // 'when' expression must be exhaustive, add necessary 'is SealedSub2' branch or 'else' branch instead
    val result  = when (o) {
        is SealedSub1 -> {}
    }
}
```

```kotlin:Hoge.kt(分岐を網羅している場合)
package ch13adt

fun hoge(o: Sealed) {
    val result  = when (o) {
        is SealedSub1 -> {}
        // コンパイルエラー：private なサブクラスにアクセスできない
        // Cannot access 'SealedSub2': it is private in file
        is SealedSub2 -> {}
    }
}
```

「シールドクラスのサブクラスの型は非公開にし、インスタンスのみファクトリメソッドで提供する」みたいな使い方ってできるのか？という疑問が動機だったが、そういう場合は非公開の型に対して `else` でひとまとめにするしかないようだ。

```kotlin:Sealed.kt
package ch13adt

sealed class Sealed

class SealedSub1: Sealed()

private class SealedSub2: Sealed()

// サブクラスのインスタンスをファクトリメソッドで提供
fun createSealed(): Sealed = SealedSub2()
```

```kotlin:Hoge.kt
package ch13adt

fun hoge(o: Sealed) {
    val result  = when (o) {
        is SealedSub1 -> {}
        else -> {}
    }
}
```

そもそもそんな設計を必要とする状況がない気もする。


## P.214 メソッドの関数参照を変数に代入する場合は変数宣言時に型の指定が必要

P.214 の最初のサンプルを試そうとして変数の型指定をサボったらコンパイルエラーになった。

```kotlin:コンパイルエラー
package chapter13

fun main() {
    // 型の指定なし
    val method = Int::inc

    // コンパイルエラー
    // Unresolved reference: method
    println(123.method())

    // こっちはOK
    println(method(123))
}
```

型を指定すると OK だった。

```kotlin:コンパイルOK
package chapter13

fun main() {
    // 型の指定あり。赤べこ本もこのコードが記載されてる。
    val method: Int.()->Int = Int::inc

    // OK
    println(123.method())

    // OK
    println(method(123))
}
```

型推論どうなってるんだろう、と思って確認するも特に問題ない気がする。

```kotlin:型を確認
package chapter13

fun main() {
    val method = Int::inc
    val method2: Int.()->Int = Int::inc

    // 全部同じですやんか。全然わからん…。
    println(method)   // => fun kotlin.Int.inc(): kotlin.Int
    println(method2)  // => fun kotlin.Int.inc(): kotlin.Int
    println(Int::inc) // => fun kotlin.Int.inc(): kotlin.Int
}
```

と悩んだ末、公式を確認したらちゃんと書いてあった。

- https://kotlinlang.org/docs/reference/reflection.html#function-references

> Note that even if you initialize a variable with a reference to an extension function, the inferred function type will have no receiver (it will have an additional parameter accepting a receiver object). To have a function type with receiver instead, specify the type explicitly:
> 
> ```kotlin
> val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty
> ```

「レシーバ付きの関数型を使うためには型を明示的に指定してね」とのこと。
型の指定なしで型推論に任せるとレシーバについてのパラメータが抜け落ちてしまう、らしい (そんな感じの理解) 。
型推論も万能じゃなかった。

ところで拡張関数についての話になってるけどメソッドも含んでるという理解でいいのかな…多分いいはずだ。


## P.328 予約語はバッククォートでエスケープできる

忘れそうなので書いておく。

Kotlin から Java のライブラリを使用する際に Java 側の識別子として Kotlin では予約語となるワードが使用されている場合、その識別子をバッククォートで囲めばエスケープされるとのこと。

P.328 では Java の `System.in` を参照するために `in` をエスケープして `` System.`in` `` にしたサンプルが提示されていた。

---

読んでて他に気になる所があれば追記する。