Kotlinの"fun Foo.bar(function: Foo.() -> Unit)"について理解したい


# きっかけ
Google I/OのKotlinの動画で以下のようなコードが登場します。
inline fun SQLiteDatabase.transaction(body: SQLiteDatabase.() -> Unit)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27388/7cb0542c-6182-e1c1-144f-db219d1ec5de.png)

Kotlinに慣れている方などは普通に読める方は読めると思いますが
自分はちょっとうん？ってなりました

---

# Kotlinでよくわからない事があるときの対応法
もちろんKotlinのドキュメントを見たりします。
そのままで理解できるのが一番いいですが、理解しにくいときは、もともとJavaをやっていた方は、Javaで考えれば何となく分かるという事があると思います。
コードを開いた状態で、Tools->Kotlin->Show Kotlin BytecodeでDecompileボタンを押すと一度バイトコードになった後にJavaに変換してみれるので、Javaのコードでどうなっているのか見ることができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27388/3fe555f6-1fa4-3a99-6220-d5f5dac3b324.png)

---

# 文法の名前がわからない時

文法の名前がわからない時などPsiViewerというIntelliJプラグインがあるのでそれを使うとどういうパースがされているのかAST(抽象構文木)を見ることが出来ます。(普通はIntelliJプラグインを作る時に利用するプラグインです。)
例えばこの例では`inline fun Hoge.f(body: Hoge.() -> Unit)`の`Hoge.() -> Unit`の部分がFUNCTION_TYPE_RECEIVERということがわかります。`FUNCTION TYPE RECEIVER kotlin`でググったりしていれば何なのかをつかむことができます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/27388/dec272a3-fcf1-fe1e-ef6d-d7f9607df31a.png)

---

# まずは普通のメソッドはどうなるか
ただの呼び出しがそのままJavaになった形です。

## Kotlin

```kotlin:MainActivity.kt
val hoge = Hoge()
hoge.fuga()
```

```kotlin:Hoge.kt
class Hoge {
    fun fuga(){
    }
}
```

## Javaへのデコンパイル

```java
Hoge hoge = new Hoge();
hoge.fuga();
```
```java
public final class Hoge {
   public final void fuga() {
   }
}
```

---

# 拡張関数(extension function)
拡張関数を使うと既存のクラスに外からメソッドを生やすことができます。

## Kotlin

以下のような拡張関数(extension function)が書いてあるFoo.ktを作ってみます。
この例ではHogeクラスにメソッドbar()を生やしています。

```kotlin:Foo.kt
fun Hoge.bar() : Unit{
    // thisがHogeクラスかのように
    // Hogeのメソッドが呼び出せる
    fuga()
}
```
そして以下のように呼び出すことができます。

```kotlin:MainActivity.kt
val hoge = Hoge()
hoge.bar()
```

Hogeクラスは一つ前の例のままです。

```kotlin:Hoge.kt
class Hoge {
    fun fuga(){
    }
}
```


## Javaへのデコンパイル

FooKtクラスにbarというstaticメソッド(Kotlinだとトップレベル関数)が作られ、それを呼び出す形に変換されます。

```java
Hoge hoge = new Hoge();
FooKt.bar(hoge);
```

\$receiverという引数でHogeが渡されてきました。
fuga()の呼び出しは、\$receiverへの呼び出しへと変わっています。

```java
public final class FooKt {
   public static final void bar(@NotNull Hoge $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.fuga();
   }
}
```

---

# 関数型(function type)を引数で渡す
function literal with receiverを見る前に普通のfunction typeを渡す場合を見ておきましょう。
function typeを引数で渡すことができます。

## Kotlin

```kotlin:MainActivity.kt
val hoge = Hoge()
// ラムダが渡せる
// hoge.fuga{println()}のように()は省略可
hoge.fuga({println()}) 
```

```kotlin:Hoge.kt
class Hoge {
    fun fuga(function: () -> Unit) {
        function()
    }
}
```

## Javaへのデコンパイル

以下のようになります。
ラムダのインスタンスのMainActivity\$onCreate\$2.INSTANCEを引数にhoge.fugaを呼び出します。


```java
hoge.fuga(MainActivity$onCreate$2.INSTANCE);
```

Hogeクラスはそのままという感じですね。

```java
public final class Hoge {
   public final void fuga(@NotNull Function0 function) {
      Intrinsics.checkParameterIsNotNull(function, "function");
      function.invoke();
   }
}
```

MainActivity\$onCreate\$2ではFunction0を実装し、println();を行います。

```java
class MainActivity$onCreate$2 extendsLambda implements Function0 {
    public static final MainActivity$onCreate$2 INSTANCE 
        = new MainActivity$onCreate$2();
    public void invoke(){
        println();
    }
}
```



<details><summary>コードの見方の詳細</summary><p>
Android Studio内のデコンパイルではうまく変換されませんでした。

```java
hoge.fuga((Function0)null.INSTANCE);
```


なので手動でバイトコードを見ることにします。

普通にクラス(MainActivity\$onCreate\$2)が生成されて、そこにINSTANCEという名前でstatic finalなフィールドにインスタンスが保持され、MainActivity\$onCreate\$2クラスのinvoke()メソッドに実装が作られるみたいです。

javap -c ./app/build/tmp/kotlin-classes/debug/com/.../MainActivity.class

```
53: getstatic     #58 // Field com/.../MainActivity$onCreate$2.INSTANCE:Lcom/.../MainActivity$onCreate$2;
...
kotlin/jvm/functions/Function0
59: invokevirtual #64 // Method com/.../Hoge.fuga:(Lkotlin/jvm/functions/Function0;)V
62: return
```

つまりこんなイメージです

```java
hoge.fuga(MainActivity$onCreate$2.INSTANCE);
```

MainActivity\$onCreate\$2が気になるので、それのバイトコードを見てみます。

javap -c ./app/build/tmp/kotlin-classes/debug/com/.../MainActivity\$onCreate\$2.class

```
final class com.....MainActivity$onCreate$2 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit> {
  public static final com.....MainActivity$onCreate$2 INSTANCE;
....
  public final void invoke();
    Code:
 0: getstatic     #24 // Field java/lang/System.out:Ljava/io/PrintStream;
 3: invokevirtual #29 // Method java/io/PrintStream.println:()V
 6: return
```

```java
class MainActivity$onCreate$2 extendsLambda implements Function0 {
    public static final MainActivity$onCreate$2 INSTANCE 
        = new MainActivity$onCreate$2();
    public void invoke(){
        println()
    }
}
```


```java
public final class Hoge {
   public final void fuga(@NotNull Function0 function) {
      Intrinsics.checkParameterIsNotNull(function, "function");
   }
}
```

</details>



---

# `fun f(body: Hoge.() -> Unit)` (function literal with receiver)を試す
言語的な詳細はここにあるようです
https://kotlinlang.org/docs/reference/lambdas.html

## Kotlin
これはfunction literal with receiverといい、使うと、引数に渡すラムダ内でthisが指定されたクラスの中のようにメソッドを呼び出すことができます。
つまり以下のラムダの中ではPiyoのクラスのメソッド内のような動きをします。
例:
![image.png](https://qiita-image-store.s3.amazonaws.com/0/27388/e9b538ea-80ab-d5b7-278c-b1bed0310b1e.png)


普通は以下のように利用します。

```kotlin:MainActivity.kt
val hoge = Hoge()
// ラムダ内で、thisがPiyoかのように
// Piyo#hogera()メソッドを呼び出せる
hoge.fuga({ hogera()}) 
```

```kotlin:Hoge.kt
class Hoge {
    fun fuga(function: Piyo.() -> Unit) {
        // ここで引数を渡さないとコンパイルエラー
        function(Piyo()) 
    }
}
```

```kotlin:Piyo.kt
class Piyo {
    fun hogera(){
    }
}
```

## Javaへのデコンパイル

MainActivity\$onCreate$2.INSTANCEを取得してhoge.fuga()を呼び出します。

```java
hoge.fuga(MainActivity$onCreate$2.INSTANCE)
```

引数にpiyoがあるので、それを使って呼び出しています。thisがうまく引数のpiyoに切り替わっているようです。

```java
class MainActivity$onCreate$2 extends Lambda implements Function1 {
    public static final MainActivity$onCreate$2 INSTANCE 
        = new MainActivity$onCreate$2();
    public void invoke(Piyo piyo){
        piyo.hogera()
    }
}
```

Hogeクラスはそのままという感じのようです。

```java
public final class Hoge {
   public final void fuga(@NotNull Function1 function) {
      Intrinsics.checkParameterIsNotNull(function, "function");
      function.invoke(new Piyo());
   }
}
```

<details><summary>コードの見方の詳細</summary><p>
一個前と同じようにAndroid Studio上では見れないのでバイトコードで見ていきます

MainActivity\$onCreate\$2を取得して、hoge.fuga()メソッドを呼び出します。

```
      51: astore_2
      52: aload_2
      53: getstatic     #58                 // Field com/github/takahirom/kotlinsupportprojectpreview6/MainActivity$onCreate$2.INSTANCE:Lcom/github/takahirom/kotlinsupportprojectpreview6/MainActivity$onCreate$2;
      56: checkcast     #60                 // class kotlin/jvm/functions/Function1
      59: invokevirtual #64                 // Method com/github/takahirom/kotlinsupportprojectpreview6/Hoge.fuga:(Lkotlin/jvm/functions/Function1;)V
```

つまり以下のようになります。

```java
hoge.fuga(MainActivity$onCreate$2.INSTANCE)
```

MainActivity\$onCreate\$2は以下のように普通にメソッドとして呼び出しを行うように変換が行われるようです。

```
final class com.....MainActivity$onCreate$2 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function1<com.....Piyo, kotlin.Unit> {
  public static final com.....MainActivity$onCreate$2 INSTANCE;
...
  public final void invoke(com.....Piyo);
    Code:
...
 7: invokevirtual #33 // Method com/.../Piyo.hogera:()V
10: return
```

Javaにすると以下のようになります。

```java
class MainActivity$onCreate$2 extendsLambda implements Function1 {
    public void invoke(Piyo piyo){
        piyo.hogera()
    }
}
```

Hogeクラスは以下のようになります。

```java
public final class Hoge {
   public final void fuga(@NotNull Function1 function) {
      Intrinsics.checkParameterIsNotNull(function, "function");
      function.invoke(new Piyo());
   }
}
```
</p>
</details>

---

# fun Hoge.f(body: Hoge.() -> Unit) (拡張関数 + function literal with receiver)
これがタイトルで行いたかったものとなります。
これまでの拡張関数(Extension)とfunction literal with receiverを組み合わせた形になります。

## Kotlin

```kotlin:MainActivity.kt
val piyo = Piyo()
// piyoのインスタンスにbarが生えている
piyo.bar { 
    // インスタンスの指定がなくてもPiyo#hogera()メソッドにアクセスできる
    hogera() 
}
```

```kotlin:Foo.kt
fun Piyo.bar(function: Piyo.() -> Unit) : Unit{
    function() // 引数がなくてもエラーにならない
}
```

```kotlin:Piyo.kt
class Piyo {
    fun hogera(){
    }
}
```

## Javaへのデコンパイル

拡張関数なので、FooKtへの呼び出しになり、引数にpiyoが渡ります。

```java
FooKt.bar(piyo, MainActivity$onCreate$2.INSTANCE);
```

渡されたpiyoを使ってfunctionのinvoke()を呼び出します。invoke()を呼び出す時にKotlinのコードでは引数を渡していませんでしたが、追加され、呼び出されました。

```java
public final class FooKt {
   public static final void bar(Piyo $receiver, Function1 function) {
...
      // 引数が追加されている！
      function.invoke($receiver);
   }
}
```

引数にpiyoがあるので、それを使って呼び出しています。thisがうまく引数のpiyoに切り替わっているようです。

```java
class MainActivity$onCreate$2 implements Function1 {
    public static final MainActivity$onCreate$2 INSTANCE 
        = new MainActivity$onCreate$2();
    public void invoke(Piyo piyo) {
        piyo.hogera();
    }
}
```

<details><summary>詳細</summary><p>
getstaticでMainActivity\$onCreate\$2.INSTANCEを取得します
FooKt.barを取得したINSTANCEを引数に呼び出します。

```
51: astore_2
52: aload_2
53: getstatic     #58 // Field com/.../MainActivity$onCreate$2.INSTANCE:Lcom/.../MainActivity$onCreate$2;
56: checkcast     #60 // class kotlin/jvm/functions/Function1
59: invokestatic  #66 // Method com/.../FooKt.bar:(Lcom/.../Piyo;Lkotlin/jvm/functions/Function1;)V
```

つまり以下のようなイメージになります。

```java
FooKt.bar(piyo, MainActivity$onCreate$2.INSTANCE);
```

FooKtでは以下のようにfunctionに対して呼び出しを行います。
ここで特殊なことは、function()と書いていたのが、第一引数のPiyoが勝手にfunction()の引数に追加されています。

```java
public final class FooKt {
   public static final void bar(@NonNull Piyo $receiver,@NonNull  Function1 function) {
...
      // 引数が追加されている！
      function.invoke($receiver);
   }
}
```

MainActivity\$onCreate\$2のinvokeメソッドは以下のようになり、Piyoが渡されて、それにメソッドを呼び出す形になります。

```
  public final void invoke(com.....Piyo);
    Code:
...
 7: invokevirtual #33 // Method com/.../Piyo.hogera:()V
10: return

```

つまり以下のような形です。

```java
class MainActivity$onCreate$2 implements Function1 {
    public void invoke(Piyo piyo) {
        piyo.hogera();
    }
}
```

</p></details>

---

# まとめ
* Javaへのデコンパイルでなんとなく分かった気がする (いいか悪いかは別として脳内でJavaに変換できそう)
* Kotlinでわからない時はデコンパイルして見てみると良いかもしれない
* 拡張関数もfunction literal with receiverもほぼ同じで、 引数にreceiverとして渡されたものをthisとして使えるように変換してくれる形で実装されている。
* 拡張関数とfunction literal with receiverを組み合わせると呼び出す時に勝手に引数を追加してくれる形で実装されている

---

# おまけ
有名な話ですが、Kotlinのスコープ関数も同じ仕組みで実装されています。
ここが個人的にKotlinで好きなところです。
今回のでJavaとして読めるはず、、

```kotlin
fun <R> run(block: () -> R): R = block()
fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
fun <T, R> T.let(block: (T) -> R): R = block(this)
```