【Kotlin】複数の値を返したい！

![Kotlin 1.7.20](https://img.shields.io/badge/Kotlin-1.7.20-blue)

関数から複数の値を返したいこと、ありますよね！？

## 2つの値を返す（`Pair` クラス）

2つの値を1つにまとめたいときは [`Pair` クラス](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/index.html)を使いましょう。

```Kotlin
// 名前とバージョンを返す関数
fun getNameAndVersion(): Pair<String, Double> {
    return Pair("Kotlin", 1.7)
}

fun main() {
    val nameAndVersion = getNameAndVersion()
    val name = nameAndVersion.first // -> "Kotlin"
    val version = nameAndVersion.second // -> 1.7
}
```

1つ目の値は [`first` プロパティ](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/first.html)、2つ目の値は [`second` プロパティ](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-pair/second.html)で、それぞれ取り出せます。

### `Pair` インスタンスを簡単に作る（`to` 関数）

[`to` 関数](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to.html)を使うと簡単に `Pair` インスタンスを作れます。

```Kotlin
fun getNameAndVersion(): Pair<String, Double> {
    return "Kotlin" to 1.7
}
```

`to` 関数は2項演算子のように2つの値の間に置けます。（[中置記法（infix notation）](https://kotlinlang.org/docs/reference/functions.html#infix-notation) と言います。）

### 返値を2つの値として受け取る（分解宣言）

2つの値を `Pair` インスタンスとしてではなく2つの値として受け取ることもできます。（[分解宣言（Destructuring Declarations）](https://kotlinlang.org/docs/reference/multi-declarations.html) と言います。[^1]）

[^1]: JavaScript では同様の機能が[分割代入（Destructuring assignment）](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)と呼ばれています。

```Kotlin
fun main() {
    val (name, version) = getNameAndVersion()
}
```

## 3つの値を返す（`Triple` クラス）

3つの値をまとめたいときは [`Triple` クラス](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/index.html)を使いましょう。

```Kotlin
val triple = Triple("Kotlin", 1.7, 2022)
```

`Pair` クラスでの `to` 関数のようなものはありません。

値はプロパティ [`first`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/first.html)、[`second`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/second.html)、[`third`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/third.html) で取り出せます。

```Kotlin
val name = triple.first // -> "Kotlin"
val version = triple.second // -> 1.7
val release = triple.third // -> 2022
```

分解宣言も使えます。

```Kotlin
val (name, version, release) = triple
```

## 4つ以上の値を返す（`data class`）

4つ以上の値をまとめる型は標準ライブラリーには用意されていません。[^2]
自作する必要がありますが、[`data class`](https://kotlinlang.org/docs/reference/data-classes.html) を使えば簡単です。

[^2]: 以前はあったとか…

```Kotlin
data class Lang(
    val name: String,
    val version: Double,
    val release: Int,
    val isGood: Boolean,
)

val kotlin = Lang("Kotlin", 1.7, 2022, true)
```

分解宣言も使えます。

```Kotlin
val (name, version, release, isGood) = kotlin
```

## 🚫アンチパターン：配列やリストを使う

`Array<Any?>` 型や `List<Any?>` 型を使えば
値がいくつでもどんな型でも返すことができますが、
絶対にやってはいけません。

理由は次の通りです。
- 各値が型安全でない
- 値の数が不足していてもコンパイルエラーにならない

`Array<T>` や `List<T>` を使うべきなのは、
要素の型が本質的に同じ[^型が本質的に同じ]で、
要素数が不定もしくは多数の場合だけです。

[^型が本質的に同じ]: コード上で同じ型で表現しているというだけでなく、本質的に同等の型であるということ。例えば長さと重さをどちらも `Double` で表していたとしても、それらは本質的には異なる型です。

## ⚠️注意：既存の型を使えないか検討する

上述の方法よりも先に、既存の型を使えないか検討しましょう。

例えば、整数を数え上げる範囲を表すなら、
`Pair<Int, Int>` 型よりも [`IntRange` クラス](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/-int-range/)を使うべきです。

## ⚠️注意：独自の型を定義することを検討する

既存の型に適切なものがなく、
`Pair` クラスや `Triple` クラスが使える場合でも、
独自の型を定義することを検討しましょう。

独自の型を定義すれば、
型名やプロパティを見ただけでそれが何を表しているか明確になりますし、
偶然型が同じになった別の `Pair` などと混同してしまうこともなくなります。

次のような場合には独自の型を定義すべきです。
- 多くの箇所で使われる
- 値の取り違えをしやすい  
    - 型が同じで順番に意味がないなど。例えば幅と高さ

## おまけ：分解宣言についてもう少し

### ラムダ式の引数にも使える

ラムダ式の引数部分でも分解宣言を使えます。

```Kotlin
listOf(
    0 to 'A',
    1 to 'B',
    2 to 'C',
).forEach { (number, alphabet) ->
    println("$number: $alphabet")
}
```

### `List` インターフェイスの第5要素までにも使える

`List` インターフェイスの第1〜第5要素には分解宣言を使えます。

```Kotlin
val list = listOf('A', 'B', 'C', 'D', 'E', 'F')
val (a, b, c, d, e) = list
```

### 分解宣言の入れ子はできない

分解宣言を入れ子にすることはできないようです。

```Kotlin
val numbers = (1 to 1L) to (1.0 to 1.0F)
val ((i, l), (d, f)) = numbers // コンパイルエラー！
```

### 自作クラスでも使えるようにできる

`Pair` クラス、`Triple` クラス、`data class`、`List` インターフェイスで分解宣言を使えるとお伝えしましたが、これらが特別扱いされているわけではありません。
自作のクラスでも使えるようにすることができます。

```Kotlin
class Lang(
    val name: String,
    val version: Double,
    val release: Int,
    val isGood: Boolean,
) {
    operator fun component1() = name
    operator fun component2() = version
    operator fun component3() = release
    operator fun component4() = isGood
}

fun main() {
    val kotlin = Lang("Kotlin", 1.7, 2022, true)
    val (name, version, release, isGood) = kotlin
}
```

前出の `Lang` クラスを `data class` ではない普通のクラスにしました。
代わりに `componentN` （`N` は 1 始まりの整数）という `operator` 関数を追加しています。
これで `data class` のときと同様に分解宣言が使えます。

このように分解宣言の `N` 番目の引数(？)にしたい値を `componentN` という名前の `operator` 関数で返すようにすれば、自作のクラスでも分解宣言を使えるようになります。

`data class` ではこの `componentN` を自動的に実装してくれます。
そして `Pair` クラスと `Triple` クラスは実は `data class` です（それぞれの API ドキュメントをご確認ください）。

／以上