Edited at

クリスマスプレゼントにはKotlin1.1が欲しいです

More than 1 year has passed since last update.

この投稿は 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プロジェクトを作成してこんな感じに設定します.


build.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を拡張して下のようにxyをプロパティとして宣言できますが,適切にスコープを切らないと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っぽいコードで失礼します.


HogeActivity.kt

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]

:innocent:try-catchでもつかえます(使うのが妥当かどうかは置いといて):innocent:

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のリリースが待ち遠しいですね!


参考にしたもの