Destructuring Declarations in Kotlin（社内勉強会用）

# Kotlin の Destructuring が微妙だという話をします

---

# Destructuring Declarations in Kotlin

公式リファレンス https://kotlinlang.org/docs/reference/multi-declarations.html より

> Sometimes it is convenient to destructure an object into a number of variables, for example:

```kotlin
val (name, age) = person
```

※ Syntax Highlight が変...

---

# Parallel Assignment in Ruby

```ruby
def hoge
  return 1, 2
end

a, b = hoge
# a=>1, b=>2
```
---

# Examples in Ruby

```ruby
a, b = [1, 2]
# a=>1, b=>2
```

```ruby
a, b, c = [1, 2]
# a=>1, b=>2, c=>nil
```

```ruby
a, b = [1, 2, 3]
# a=>1, b=>2
```

```ruby
a, *b = [1, 2, 3]
# a=>1, b=>[2, 3]
```

```ruby
*a, b = [1, 2, 3]
# a=>[1, 2], b=>3
```

```ruby
*, b = [1, 2, 3]
# b=>3
```

---

# Arrayのみが対象 in Ruby

Hash から Parallel Assignment したりはできない

```ruby
h = { a: 1, b: 2 }
a, b = h
# a=>{a:1, b:2}, b=>nil
```

---

# Destructuring assignment in ES6

Array destructuring assignment

```javascript
[a, b] = [1, 2];
// a=>1, b=>2
```

```javascript
[, b] = [1, 2, 3];
// b=>2
```

```javascript
[, ...b] = [1, 2, 3];
// b=>[2, 3]
```
---

# Object destructuring in ES6

* 名前が一致していること

```javascript
var o = { a: 1, b: 2 };
var {a, b} = o;
// a=>1, b=>2
var {x, y} = o;
// x=>undefined, y=>undefined
```

```javascript
var o = { a: 1, b: 2 };
var {a: alpha, b: bravo} = o;
// alpha=>1, bravo=>2
```

* importで重宝したりするみたい

```javascript
import {foo, bar} from "module"
```

---

# Destructuring Declarations in Kotlin

stdlibで定義されているPairを使った例

```kotlin
data class Pair<A, B>(first: A, second: B)

val p = Pair(first = 1, second = 2)
// p.first=>1, p.second=2
val (a, b) = p
// a=>1, b=>1
```

蛇足: AndroidにもPairクラスがあり、まあまあ使われている。

```java
package android.util;

public class Pair<F, S> {
  public final F first;
  public final S second;
}
```

---

# 別に destructure がないと困ることもないよね？

例: 値を返すが、失敗時はエラーを、キャンセルされた場合はその旨を、戻り値で返したい。

```kotlin
public sealed class Result {
    data class Ok(val data: String): Result()
    data class Error(val error: Error): Result()
    object Canceled: Result()
}

val result = doSomething()
when (result) {
    is Result.Ok -> print(result.data)
    is Result.Error -> result.error.printStackTrace()
    is Result.Canceled -> print("canceled")
}
```


---

# あくまで Declaration

```kotlin
var a: Int
var b: Int
(a, b) = Pair(first = 1, second = 2) // compile error
```

宣言時以外には使用できない。

---

# destructuring は componentN() 関数の呼び出し

```kotlin
val p = Pair(1, 2)
val (a, b) = p
```

```kotlin
val p = Pair(1, 2)
val a = p.component1()
val b = p.component2()
```

data class（この例だとPairクラス）が、componentN()関数を自動的に定義するため、このようなことが可能。

---

# 定義順が変更されると、当然壊れる

```kotlin
data class Person(
  var name: String,
  var age: Int
)

val p = Person(name = "yuki.terai", age = 17)
val (name, age) = p
// name=>"yuki.terai"
```

上記を、data classでのプロパティ宣言順だけ変えても、コンパイルは通る。

```kotlin
data class Person(
  var age: Int,
  var name: String
)

val p = Person(name = "yuki.terai", age = 17)
val (name, age) = p
// name=>17
```

---

# 通常のclassでも可能

```kotlin
class Hoge(
  var foo: Int,
  var bar: Int
) {
  operator fun component1() = foo
  operator fun component2() = bar
  operator fun component3() = "baz"
}

var (a, b, c) = Hoge(1, 2)
// a=>1, b=>2, c="baz"
```

* componentN() 関数が定義されていればコンパイルが通る。
* 演算子を実装するためのoperator関数として、言語仕様でpredefinedされている。

---

# predefined ということはNは無限ではない？

```kotlin
data class Hoge(
    var first: Int,
    var second: Int,
    var third: Int,
    var fourth: Int,
    var fifth: Int,
    var sixth: Int,
    var seventh: Int,
    var eighth: Int,
    var ninth: Int,
    var tenth: Int
)

val hoge = Hoge(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val (a, b, c, d, e, f, g, i, j, k) = hoge
// k=>10
```
---

# arrayだとどうなるのか

```kotlin
val list = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val (a, b, c, d, e, f, g, i, j, k) = list
```

* Compile Errorとなるorz
  * Destructuring declaration initializer of type List<Int> must have a 'component6()' function
* collectionでは、component5() までしか定義されていないっぽい？

---

# rest in object 的な宣言ができない

```kotlin
var (a, _) = arrayOf(1, 2, 3)
// a=>1
```

上記のように `_` を使ったり定義しなかったりで捨てることはできるが、残りをarrayで受け取る手段がない

```kotlin
fun concat(vararg strings: Int): String {
  return strings.joinToString()
}

val a = arrayOf("hello", "world")
val str = concat(*a)
```

可変長引数 `vararg` のために展開する `spread operator` はあるので、destructuringでも同様にやってはどうかというアイディアはある。

```kotlin
var (a, *x) = arrayOf(1, 2, 3)
// !! Compile error for now
```

---

# まとめ

* Kotlin の Destructuring Declarations は微妙な点も多く、いまいち使い所がわからない。
* Kotlin 1.3 現在だが、今後に期待なのだろうか？