この投稿は Retty inc. Advent Calendar 17日目の記事です.
昨日はSatoshi Nishinaka氏(@satoshi_nishinaka)のLet's Enjoy PhantomJSでした.
皆さんこんにちは.Retty inc.のSakataです.Kotlinの1.0がリリースされた2016年も残すところあと二週間となりました.先月の末,Kotlin 1.1の三番目のマイルストーンリリース,1.1-M03がありましたね.皆さんはもう試しましたか?今回はこの1.1-M03とKotlin 1.1の機能の一つであるcoroutineのプロトタイプ,kotlinx.coroutinesを実際に使いながらただでさえかわいいKotlinが1.1で更に魅力的になるのを楽しみたいと思います.
はじめに
この記事はKotlin BlogをもとにKotlin1.1-M03の時点で利用することができる機能をかんたんにまとめたものです.
This is not a stable version of Kotlin, and no compatibility guarantees are given here: in the future previews of 1.1, syntax, APIs, command-line switches and anything else may be changed. If you need a stable version of Kotlin, please stay on 1.0.X until further notice.
ということで後々ここで紹介したサンプルコードは**動かなくなるかもしれません.**引用したソースコードは明記しています.
準備
まずはIDEAでKotlin1.1とkotlinx.corutinesを使えるようにします.gradleプロジェクトを作成してこんな感じに設定します.
group 'io.github.yusaka39'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.1-M03'
repositories {
mavenCentral()
maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = 'io.github.yusaka39.demos110.MainKt'
repositories {
mavenCentral()
maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1' }
maven { url "http://dl.bintray.com/kotlin/kotlinx.coroutines" }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-generate:0.1-alpha-2'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-async:0.1-alpha-2'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-rx:0.1-alpha-2'
}
また,[Tools] -> [Kotlin] -> [Configure Kotlin Plugin Updates] から EAP 1.1 を選んでpluginをアップデートしておくとIDEのサポートが効いて楽ちんです.
新しい文法
まずは文法レベルでの変更を紹介したいと思います.
型エイリアス
いわゆる型シノニムです.既存の型に別名をつけることができます.
typealias Point = Pair<Double, Double>
fun square(x: Double) = x * x
fun calcDistance(from: Point, to: Point): Double {
val (fromX, fromY) = from
val (toX, toY) = to
return Math.sqrt(square(toX - fromX) + square(toY - fromY))
}
今回の例だとPoint
を拡張して下のようにx
とy
をプロパティとして宣言できますが,適切にスコープを切らないとPair<Double, Double>
にも宣言されて(あくまでもただの同義語)キモいので注意が必要です.
val Point.x: Double
get() = this.first
val Point.y: Double
get() = this.second
(0.0 to 0.0).x // legal
インスタンスからメンバ参照
今までもHogeClass::method
でクラスのメソッドを参照することができましたが,instanceOfHoge::method
でレシーバがバインドされたメソッドを得ることができるようになりました.
val fruits = listOf("apple", "orange", "peach")
fruits.filter("apple"::equals) // ["apple"]
ローカルなdelegated property
classのメンバ以外の場所でもdelegated propertyが使えるようになりました.Androidっぽいコードで失礼します.
class HogeActivity : AppCompatActivity() {
@Suppress("unchecked_cast")
fun <T> bindView(resId: Int) = lazy {
this.findViewById(resId) as T
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.hoge)
val titleText: TextView by bindView(R.id.titleText)
titleText.text = "hello"
}
}
今までdelegateしたいがためにクラスのプロパティとして宣言していたもののスコープが絞れてハッピーな感じです.
lambdaでdataな引数を分解
今までも下のようなコードはリーガルでした.
data class Triple<F, S, T>(val first: F, val second: S, val third: T)
fun sum(triple: Triple<Int, Int, Int>): Int {
val (f, s, t) = triple
return f + s + t
}
1.1-M03では次のようなコードも許容されます.
val lst: List<Triple<Int, Int, Int>> = // Some initialize codes
lst.map { (f, s, t) -> f + s + t }
変数宣言でアンスコ
データクラスを分解したり複数の引数を取るlambdaを使うケースで使わない変数をgolangとかっぽく_
にできるようになりました.
val lst: List<Pair<Hoge, Fuga>> = // Some initialize codes
lst.forEach { (_, fuga) -> doSomethingWithFuga(fuga) }
val (hoge, _) = lst.filterIndexed{ i, _ -> i % 2 == 0 }[0]
try-catchでもつかえます(使うのが妥当かどうかは置いといて)
try {
executeQuery(query)
} catch (_: SqlException) {
// Ignore
}
数値でアンスコ
数値リテラルの中にアンスコを書いて桁区切りしたりできるようになりました.
10_000_000 // legal
23_57 // legal
23__57 // legal
23_57.0 // legal
23_57f // legal
0x2_357 // legal
_2357 // illegal
2357_ // illegal
stdlib
便利な子が増えました.
Enum向けジェネリック関数
reifiedな型変数からenumのvalueやvaluesを取ってこれるトップレベル関数
enumValues()
,enumValueOf()
が生えました.
DSL開発用にスコープをタイトにするやつ
今までは次のようなケースが起こりうる問題がありました.
table {
tr {
tr {} // PROBLEM: Table.tr() is valid here
}
}
これはhtmlビルダの例ですが,tr
(outer)の中にtr
(inner)は入ってほしくないがtr
(outer)からtable
のコンテキストが見えてしまうため,Kotlinの文法的に合法にtr
(inner)が呼び出せてしまいます.これの問題を解決するために@DslMarker
アノテーションが追加されました.このアノテーションで注釈されたアノテーションで注釈されたクラスから同じアノテーションがついているクラスのコンテキストが見えなくなります.~~自分で書いてて訳がわからなくなってきた.~~簡単なhtmlビルダの例を示します.
アノテーションなしの場合
fun html(initializer: Html.() -> Unit) = Html(initializer)
class Html(initializer: Html.() -> Unit) {
lateinit var header: Header
init {
this.initializer()
}
fun header(initializer: Header.() -> Unit) {
this.header = Header(initializer)
}
}
class Header(initializer: Header.() -> Unit) {
init {
this.initializer()
}
}
html {
// this@Htmlが見えている
header {
// this@Headerとthis@Htmlが見えている
header { // legal
}
}
}
アノテーション有りの場合
@DslMarker
annotation class HtmlMarker
fun html(initializer: Html.() -> Unit) = Html(initializer)
@HtmlMarker
class Html(initializer: Html.() -> Unit) {
lateinit var header: Header
init {
this.initializer()
}
fun header(initializer: Header.() -> Unit) {
this.header = Header(initializer)
}
}
@HtmlMarker
class Header(initializer: Header.() -> Unit) {
init {
this.initializer()
}
}
html {
// this@Htmlが見えている
header {
// this@Headerが見えている
header { // Html#header()が見えないのでillegal
}
}
}
kotlinx.html repositoryでも使っているようです.
Coroutine
コルーチンがKotlinにくる!
すでに独自の実装を生み出している人とか居そうですが最終的にstdlibに入ってくるようです.
async/await
fun getRestaurant(id: Long) = CompletableFuture.supplyAsync {
// 通信が入る処理
}
fun getRestaurantsById(ids: List<Long>) = async<List<Restaurant>> {
ids.map { getRestaurant(it) } // 通信を並列で実行
.map { await(it) } // 通信がすべて完了したらidに対応する順にListに突っ込む
}
fun showRestaurants(ids: List<Long>) = asyncUI {
await(getRestaurantsById(ids)).let {
setRestaurantsToView(it) // 非同期で待機しつつ終わったらUIスレッドでViewを操作
}
}
イイね!
yield
iterableなsomethingの生成を簡単にしてくれるイケてる奴です.見かけ上無限のものも作れます.型が許す限り正しいフィボナッチ数列になるものを生成してみます.
fun fib() = generate<Long> {
var acc1 = 0L
var acc2 = 1L
while (true) {
yield(acc1)
val tmp = acc1
acc1 = acc2
acc2 += tmp
}
}
(0 until 10).zip(fib().asIterable())
// [(0, 0), (1, 1), (2, 1), (3, 2), (4, 3), (5, 5), (6, 8), (7, 13), (8, 21), (9, 34)]
ちなみに下の例は怒られます.
fun fib() = generate<Long> {
tailrec fun iter(acc1: Long, acc2: Long) {
yield(acc1)
iter(acc2, acc1 + acc2)
}
iter(0L, 1L)
}
まとめ
Kotlinを書いててできたら良いなと思っていたあんな機能やこんな機能が盛り込まれててStableのリリースが待ち遠しいですね!