先週、 Redmadrobot SPBの支援により、 Ketlin User Group SPBの一部として、JetBrainsの開発者であるStanislav Erokhinとのミーティングが開催されました。 会議で、彼はKotlinの次のメジャーバージョン(1.3未満)の開発機会に関する情報を共有しました。
この記事では、受け取った資料をまとめ、計画について話します。
重要です。 説明するすべての機能の設計は、認識を超えて変更できます。 以下はすべてチームの現在の計画ですが、開発者はいかなる保証も行いません。
はじめに
Kotlinの次のメジャーバージョン(1.3)では、多くの変更が予定されています。 彼らはそのうちのいくつかについてだけ語ってくれました。
主な方向:
- コルーチン
- インラインクラス
- 符号なし算術
- Javaのデフォルトメソッド
- DFA(データフロー分析)およびスマートキャストのメタ情報
- 型推論を制御するための注釈
- SAM for Javaメソッド
- Kotlinメソッドおよびインターフェイス用のSAM
- ビルダー向けのスマートな推論
- コンパイラスキーマの変更
コルーチン
バージョン1.3では、待望のKotlinでのコルチンのリリースが計画されており、その結果、 kotlin.coroutines.experimentalパッケージからkotlin.coroutinesに移行します。
後で、既存のCorutin APIをサポートするサポートライブラリをリリースする予定です。
コルーチンの大きな変更
- JetBrainsはパフォーマンスに積極的に取り組んでいます。 たとえば、新しいバージョンでは、 ステートマシンは必要のない場所では生成されないことが計画されています。 このため、生成されるオブジェクトの数は減少します
- インライン機能の完全な一時停止サポートに関する作業が進行中です
- サスペンド機能の呼び出し可能な参照のサポートが追加されました
-
Continuation
インターフェースはContinuation
されContinuation
現時点では、 Continuation
は結果またはエラーのいずれかを転送できる2つのメソッドが含まれています。
interface Continuation<in T> { fun resume(value: T) fun resumeWithException(exception: Throwable) }
新しいバージョンでは、何らかの形式の値またはエラーを含む型指定されたResult
クラスを使用する予定であり、 Continuation
では1つのresume(result: Result<T>)
メソッドをresume(result: Result<T>)
:
class Result<out T> { val value: T? val exception: Throwable? } interface Continuation<in T> { fun resume(result: Result<T>) }
開発者は各値/エラー転送の前にチェックを実行する必要がないため、このような実装により、コルーチン用のライブラリを開発するのがより便利になります。 このソリューションのAPIは間違いなく変更されますが、意味は変わりません。
残念ながら、このソリューションには問題があります。 Kotlinの現在の実装ですべてが機能するためには、各値をラッパーResult
でラップする必要があります。これにより、 resume
呼び出しごとに追加のオブジェクトが作成されます。 この問題の解決策は、 インラインクラスです。
インラインクラス
Result
例:
inline class Result<out T>(val o: Any?) { val value: T? get() = if (o is Box) null else o as T val exception: Throwable? get() = (o as? Box).exception } private class Box(val exception: Throwable)
インラインクラスの利点は、上記の例に従って、実行時にオブジェクトがコンストラクタに渡されたval o
として単純に保存され、 exception
がBox
ラッパーに残ることです。 これは、エラーが非常にまれにしかスローされず、ラッパーの生成がパフォーマンスに影響しないためです。 ゲッターvalue
とexception
は静的メソッドに生成され、ラッパー自体は消えます。 これにより、 Continuation.resume
呼び出すときに冗長オブジェクトを作成する問題が解決されます。
インラインクラスの制限
- 実行時にラッパーが消去され、フィールド値のみが残るため、フィールドが1つだけのインラインクラスを作成することも可能です。
- ジェネリックが使用される場合、
Result
はラップされます。これは、アンパック後、ラップされたオブジェクトが返されたかどうかが不明であるためです(たとえば、List<Result>
を使用した場合)
符号なし算術
インラインクラスの外観の考えられる結果の1つは、 符号なし型です。
inline class UInt(val i: Int) inline class ULong(val l: Long) ...
標準ライブラリでは、各プリミティブについて、対応する符号なしバリアントが記述されます。このバリアントでは、算術演算子を使用した作業が実装されるほか、 toString()
などの基本メソッドも実装されます。
リテラルサポートと暗黙的な変換も追加されます。
デフォルトの方法
標準実装のメソッドは、Javaよりも早くKotlinのインターフェースに登場しました。
インターフェイスがKotlinで記述され、実装がJava 8である場合、標準実装のメソッドでは@JvmDefault
アノテーションを使用できる@JvmDefault
、Javaの観点からこのメソッドはdefault
としてマークされdefault
。
DFAおよびスマートキャストのメタ情報
開発中のもう1つの興味深い点は、DFA(データフロー分析)およびスマートキャストのメタ情報を追加できる契約です。 例を考えてみましょう:
fun test(x: Any) { check(x is String) println(x.length) }
check(Boolean)
関数は、 x
String
型であることを確認した結果を渡します。 このレベルでは、この関数が内部で何を行うかは不明であるため、次のコード行でエラーが発生します(この場合、 スマートキャストは不可能です)。 コンパイラは、 x
がString
あることを確認できません。 契約は彼がこれを説明するのを助けます。
コントラクトでのcheck
実装は次のとおりです。
fun check(value: Boolean) { contract { returns() implies value } if (!value) throw ... }
このメソッドが結果を返す場合、この結果は正確にtrue
というかなり具体的な構文で、 contract
関数呼び出しがここに追加されました。 そうでない場合、例外がスローされます。
このような契約のおかげで、コンパイラーはx
に対してスマートキャストを実行でき、 println(x.length)
呼び出してもエラーは発生しません。
不明なタイプの状況を解決することに加えて、契約は他の問題を解決します。 例:
fun test() { val x: String run { x = "Hello" } println(x.length) } fun <R> run(block: () -> R): R { contract { callsInPlace(block, EXACTLY_ONCE) } return block() }
run
関数のコントラクトは、渡されたblock
が1回だけ呼び出され、定数x
正しく初期化され、 x.length
がエラーなしで実行されることをコンパイラに伝えます。
コントラクトはコンパイラーによって使用されますが、コンパイルの結果のコードには影響しません。 すべてのコントラクトはメタ情報に変換され、特定の関数のいくつかの仮定をコンパイラーに通知します。
IDEは、可能な場合、契約と自動生成の特定の強調表示を計画します。
型推論を制御するための注釈
Kotlin stdlibには、内部でのみ使用されるさまざまな機能が既にあります。 開発者はそれらのいくつかを共有する準備ができています。 新しいアノテーションとそれらが提供する機能を検討してください。
@NoInfer
@NoInfer
は、 型推論を少し賢くするように設計されています。
filterIsInstance
メソッドを使用してコレクションをフィルタリングするとします。 このメソッドでは、フィルター処理を実行する型を指定することが重要ですが、コンパイラーは型を登録せずに呼び出しを許可できます(推定しようとします)。 署名で@NoInfer
を使用すると、タイプのない呼び出しはエラーとして目立ちます。
fun print(c: Collection<Any>) { ... } val c: Collection<Any> print(c.filterIsInstance()) // print(c.filterIsInstance<String>()) fun <R> Iterable<*>.filterIsInstance(): List<@NoInfer R>
@Exact
この注釈は@NoInfer
に非常に似てい@NoInfer
。 彼女は、型が指定されたものと正確に等しくなければならない、つまり「サブタイプ」でも「スーパータイプ」でもないことを報告します。
@OnlyInputTypes
この注釈は、ユーザーが引数またはレシーバーに持っていたタイプのみが推論結果であることを示します。
SAM for Javaメソッド
開発中の別の機能により、SAMの操作が簡単になります。 例を考えてみましょう:
//Java static void test(Factory f, Runnable r) interface Factory { String produce(); } //Kotlin fun use(f: Factory) { test(f) { } // Factory != () -> String }
Javaは、2つの機能インターフェイスを受け入れるメソッドを宣言します。 Kotlinレベルでは、インターフェイスの実装オブジェクトと、これらのインターフェイスのメソッドのシグネチャに対応するラムダ式の両方を任意の組み合わせで渡して、このメソッドを呼び出すことができるはずです。
1. test(Factory, Runnable) 2. test(Factory, () -> Unit) 3. test(() -> String, Runnable) 4. test(() -> String, () -> Unit)
現在、オプション2と3は不可能です。 Kotlinコンパイラは、2つのオプションのみを許可します。1つは2つのインターフェイスを受け入れ、もう1つは2つのラムダを受け入れます。
コンパイラの現在の実装で4つのオプションすべてを可能にすることは難しい作業ですが、不可能ではありません。 新しいタイプの推論システムは、このような状況をサポートします。 環境では1つのfun test(Factory, Runnable)
関数のみが表示されますが、ラムダとインターフェイス実装オブジェクトの両方を任意の組み合わせで転送できます。
Kotlinメソッドおよびインターフェイス用のSAM
Kotlinインターフェースを受け入れるメソッドがKotlinで受け入れられる場合、自動変換を行うには(インターフェースの実装とラムダの両方を転送できます)、キーワードsam
インターフェースをマークする必要があります。
sam interface Predicate<T> { fun test(t: T): Boolean } fun Collection<T>.filter(p: Predicate<T>): Collection<T> { ... } fun use() { test { println("Hello") } val l = listOf(-1, 2, 3) l.filter { it > 0 } }
Javaインターフェースの場合、変換は常に機能します。
ビルダー向けのスマートな推論
ビルダーを書きたいと想像してください:
fun <T> buildList(l: MutableList<T>.() -> Unit): List<T> { ... }
そして、次のように使用します。
val list = buildList { add("one") add("two") }
T
型T
add(String)
呼び出しから推測されないため、現時点ではこれは不可能です。 したがって、次のように記述する必要があります。
val list = buildList<String> { add("one") add("two") }
最初のオプションを(タイプを明示的に指定せずに)使用できるようにする予定です。
コンパイラスキーマの変更
JetBrainsは、Kotlin Nativeに積極的に取り組んでいます。 その結果、別のリンクがコンパイラスキームに登場しました- バックエンド内部表現(BE IR) 。
BE IRは、ソースコードのすべてのセマンティクスを含む中間表現であり、システムのバイナリコードを含む、任意のプラットフォームの実行可能ファイル用にコンパイルできます。 現在、BE IRはKotlin Nativeでのみ使用されていますが、セマンティクスに関する追加情報を含むPSIではなく、すべてのプラットフォームで使用される予定です。 JVMとJSのプロトタイプは既にあり、それらは積極的に開発されています。
その結果、すべてのソースコードがBE IRに変換され、次にターゲットプラットフォームの実行可能ファイルに変換されます。
まとめ
上に書いたように、どのイノベーションがリリースに到達するか、どのような形式、どのバージョンの言語であるかはわかりません。 Kotlinチームの現在の計画のみがありますが、 開発者は保証しません :
- Corutin APIリリースとファイナライズ-1.3
- インラインクラス-1.3の実験的機能
- 符号なし算術-1.3または1.4の実験的機能
- @JvmDefault-1.2.xの実験的機能、1.3のリリース
- DFAおよびスマートキャストのメタ情報-1.3で部分的にリリース
- 型推論を制御するための注釈-1.2.xの実験的機能
- 新しいタイプの推論エンジン(ビルダーおよびSAMのスマート推論を含む)-1.3の実験的機能
Kotlinは、間違いなく確立されたプログラミング言語ですが、ユーザーベースの増加に伴い、そのアプリケーションの範囲は拡大しています。 新しい要件、プラットフォーム、およびユースケースが出現しています。 維持したい古いコードが蓄積されています。 この言語の開発者は困難や問題を理解しており、その結果、新しいバージョンではより多くの状況でそれらを使用することがさらに便利になります。