Edit Page

インライン関数

高階関数を使用すると、特定のランタイムペナルティを課せられます。各関数はオブジェクトであり、それはクロージャ、すなわち、関数の本体でアクセスされるそれらの変数をキャプチャします。メモリ割り当て(関数オブジェクトとクラス用の両方)と仮想呼び出しは、実行時のオーバーヘッドを招きます。

しかし、多くの場合、オーバーヘッドのこの種のラムダ式をインライン化することによって解消することができると思われます。以下に示す関数は、このような状況の良い例です。すなわち、lock() 関数は、簡単に呼び出し箇所でインライン化することができました。次のケースを考えてみます。

lock(l) { foo() }

パラメータやコールの生成のために関数オブジェクトを作成する代わりに、コンパイラは次のコードを放出する可能性があります:

l.lock()
try {
  foo()
}
finally {
  l.unlock()
}

それは我々が当初から欲しかったものではないですか?

コンパイラがこれをできるようにするために、 inline 修飾子で lock() 関数をマークする必要があります:

inline fun lock<T>(lock: Lock, body: () -> T): T {
  // ...
}

inline 修飾子は、関数自体やそれに渡されたラムダの両方に影響を与えます。それらのすべては、呼び出し箇所の中でインライン化されます。

インライン化では生成されるコードが大きくなる可能性がありますが、合理的な方法で(大きな関数をインライン化しないで)実行すると、特にループ内の 「メガモーフィック (megamorphic)」な呼び出し箇所でパフォーマンスが向上します。

noinline

インライン関数に渡されたラムダのうちのいくつかだけをインライン関数にしたい場合は、関数パラメータのいくつかに noinline 修飾子を付けることができます:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
  // ...
}

Inlinableラムダは、インライン関数内でのみ呼び出すことができます。または、インライン展開可能な引数として渡すこともできますが、 noinline は、好きなように操作できます。例えば、フィールドに保持したり、誰かに渡したり等。

インライン関数にインライナブル関数パラメータがなく、具体化型パラメータが指定されていない場合、コンパイラは警告を発します。このような関数のインライン展開は有益ではないためです(インライン展開が必要な場合は警告を抑制できます)。

非局所リターン

Kotlinでは、名前付き関数または無名関数を終了するには、通常、ラベル無し return のみを使用することができます。これは、ラムダを終了するにはラベルを使用しなければならず、ラムダが自身を内包する関数からの return を作ることができないため、ラムダ内での裸のリターンは禁止されていることを意味します。

fun foo() {
  ordinaryFunction {
     return // エラー: `foo` をここで return することはできない
  }
}

しかし、ラムダがインライン化されるために渡された関数の場合は、リターンも同様にインライン化することができ、それが許可されています。

fun foo() {
  inlineFunction {
    return // OK: ラムダはインライン
  }
}

(ラムダに位置するが、内包する関数から抜ける)このようなリターンは、 非局所リターン と呼ばれています。私たちは、インライン関数がしばしば内包するこのようなループの構造に慣れています。

fun hasZeros(ints: List<Int>): Boolean {
  ints.forEach {
    if (it == 0) return true // hasZeros から return する
  }
  return false
}

インライン関数の中には、渡されたラムダを、関数本体から直接ではなく、ローカルオブジェクトやネストされた関数などの別の実行コンテキストからのパラメータとして呼び出すものがあります。このような場合には、非局所制御フローもラムダでは許可されません。それを示すために、ラムダパラメータを crossinline 修飾子でマークする必要があります

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}

breakcontinue はインライン化されたラムダではまだ利用できませんが、我々はそれらをサポートすることを計画しています。

具体化型パラメータ (Reified type parameters)

時にはパラメータとして渡された型にアクセスする必要があります。

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p?.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T
}

ここでは、ツリーをたどってリフレクションを使用して、ノードに特定のタイプがあるかどうかを確認します。全く問題はないのですが、呼び出し箇所はそれほど美味しくなりません:

myTree.findParentOfType(MyTreeNodeType::class.java)

私たちが実際にしたいのはこの関数に型を渡すだけ、すなわち、このように呼び出すだけです:

myTree.findParentOfType<MyTreeNodeType>()

これを有効にするには、インライン関数が 具体化型パラメータ をサポートするので、私たちはこのように書くことができます:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p?.parent
    }
    return p as T
}

私たちは reified 修飾子で型パラメータを修飾しました。これで、関数内でアクセス可能になり、これは通常のクラスと同じように機能します。関数はインライン化されているので、リフレクションは必要ありません。 !isas のような通常の演算子が動作するようになります。また、前述したようなやりかたで呼び出すことができます:myTree.findParentOfType<MyTreeNodeType>()

リフレクションは多くの場合に必要とされないかもしれませんが、まだ具体化型パラメータでそれを使用することができます:

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
  println(membersOf<StringBuilder>().joinToString("\n"))
}

通常の機能(インラインとしてマークされていない)は具体化パラメータをもつことはできません。実行時表現を持たない型(例えば、非reified型パラメータや Nothing のような架空の型)は、reified 型のパラメータの引数として使用できません。

低レベルの説明については、仕様書を参照してください。