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:
val (name, age) = person
※ Syntax Highlight が変...
Parallel Assignment in Ruby
def hoge
return 1, 2
end
a, b = hoge
# a=>1, b=>2
Examples in Ruby
a, b = [1, 2]
# a=>1, b=>2
a, b, c = [1, 2]
# a=>1, b=>2, c=>nil
a, b = [1, 2, 3]
# a=>1, b=>2
a, *b = [1, 2, 3]
# a=>1, b=>[2, 3]
*a, b = [1, 2, 3]
# a=>[1, 2], b=>3
*, b = [1, 2, 3]
# b=>3
Arrayのみが対象 in Ruby
Hash から Parallel Assignment したりはできない
h = { a: 1, b: 2 }
a, b = h
# a=>{a:1, b:2}, b=>nil
Destructuring assignment in ES6
Array destructuring assignment
[a, b] = [1, 2];
// a=>1, b=>2
[, b] = [1, 2, 3];
// b=>2
[, ...b] = [1, 2, 3];
// b=>[2, 3]
Object destructuring in ES6
- 名前が一致していること
var o = { a: 1, b: 2 };
var {a, b} = o;
// a=>1, b=>2
var {x, y} = o;
// x=>undefined, y=>undefined
var o = { a: 1, b: 2 };
var {a: alpha, b: bravo} = o;
// alpha=>1, bravo=>2
- importで重宝したりするみたい
import {foo, bar} from "module"
Destructuring Declarations in Kotlin
stdlibで定義されているPairを使った例
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クラスがあり、まあまあ使われている。
package android.util;
public class Pair<F, S> {
public final F first;
public final S second;
}
別に destructure がないと困ることもないよね?
例: 値を返すが、失敗時はエラーを、キャンセルされた場合はその旨を、戻り値で返したい。
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
var a: Int
var b: Int
(a, b) = Pair(first = 1, second = 2) // compile error
宣言時以外には使用できない。
destructuring は componentN() 関数の呼び出し
val p = Pair(1, 2)
val (a, b) = p
val p = Pair(1, 2)
val a = p.component1()
val b = p.component2()
data class(この例だとPairクラス)が、componentN()関数を自動的に定義するため、このようなことが可能。
定義順が変更されると、当然壊れる
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でのプロパティ宣言順だけ変えても、コンパイルは通る。
data class Person(
var age: Int,
var name: String
)
val p = Person(name = "yuki.terai", age = 17)
val (name, age) = p
// name=>17
通常のclassでも可能
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は無限ではない?
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だとどうなるのか
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 must have a 'component6()' function
- collectionでは、component5() までしか定義されていないっぽい?
rest in object 的な宣言ができない
var (a, _) = arrayOf(1, 2, 3)
// a=>1
上記のように _
を使ったり定義しなかったりで捨てることはできるが、残りをarrayで受け取る手段がない
fun concat(vararg strings: Int): String {
return strings.joinToString()
}
val a = arrayOf("hello", "world")
val str = concat(*a)
可変長引数 vararg
のために展開する spread operator
はあるので、destructuringでも同様にやってはどうかというアイディアはある。
var (a, *x) = arrayOf(1, 2, 3)
// !! Compile error for now
まとめ
- Kotlin の Destructuring Declarations は微妙な点も多く、いまいち使い所がわからない。
- Kotlin 1.3 現在だが、今後に期待なのだろうか?