ことりんと一緒 - 7. Scoping Function

## 概要 / 説明
「[ことりんと一緒 Springもね - 6. 非同期処理](https://qiita.com/shinyay/items/5ad1e60435dfa9818c27)」の中で、インスタンスのプロパティ設定を行う際に以下のような記述を行いました。

```
    @Bean
    fun normalTaskExecutor(): TaskExecutor  = ThreadPoolTaskExecutor().apply {
        corePoolSize = 1
        setQueueCapacity(5)
        maxPoolSize = 1
        setThreadNamePrefix("NormalThread-")
        setWaitForTasksToCompleteOnShutdown(true)
    }
```

これは、ThreadPoolTaskExecutor インスタンスを生成し、続けてラムダでそのインスタンスに対して操作を行っています。

このように、インスタンスに続けてラムダで操作を行う際に使用する関数を **スコープ関数** と呼びます。
この書式を利用すると以下のような点で便利です。

- インスタンスへの操作をラムダ内にまとめられる
- コードの冗長性を省ける
- 操作の影響範囲をラムダ内だけに収められる

Kotlin には、このスコープ関数が複数あります。
それぞれ使い方が異なりますので、どういうものか確認してみます。

## 前提 / 環境
以下の環境で、実施していますが特に前提ではありません。

### ランタイムバージョン
- Kotlin : 1.3.0
- SpringBoot : 2.1.0.RELEASE

### Spring Dependencies
- Web
- Actuator

### 開発環境
- OS : Mac
- IDE : IntelliJ IDEA
- Build : Gradle

## 手順 / 解説
スコープ関数には以下のようなものがあります。

- with
- run
- let
- apply
- also

それぞれの関数を見ていきます。

### スコープ関数の定義
まず、それぞれのスコープ関数の定義を見てみます。

| Scoping Function| 定義 | 例 |
|:--:|:--|:--|
| with | fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block() |val r: R = with(T()) { this.foo(); this.toR() }|
| run | fun <T, R> T.run(block: T.() -> R): R = block() |val r: R = T().run { this.foo(); this.toR() }|
| let | fun <T, R> T.let(block: (T) -> R): R = block(this) |val r: R = T().let { it.foo(); it.toR() }|
| apply | fun <T> T.apply(block: T.() -> Unit): T { block(); return this } |val t: T = T().apply { this.foo() }|
| also | fun <T> T.also(block: (T) -> Unit): T { block(this); return this } |val t: T = T().also { it.foo() }|

定義を見ると大きく以下の２つの観点での定義の仕方が異なっている事が分かります。

- レシーバの表現
    - `T.() -> R` : レシーバ型Tの拡張関数
    - `(T) -> R` : レシーバ型Tを引数として扱う
- 戻り値
    - `R = block()` : ラムダで実施した結果を返却
    - `return this` : レシーバを返却

上記の事をまとめると、次のようなマトリクスになります。

![scopingfunction.png](https://qiita-image-store.s3.amazonaws.com/0/127983/24040d54-e65b-a6c4-7ea3-e584a3120148.png)

### コードサンプル
レシーバオブジェクトして次のデータクラスを用意しておきます。

```kotlin
data class Person(
        var name: String,
        var age: Int
) {
    private lateinit var hobby: String
    fun nameToUpperCase() {
        name = name.toUpperCase()
    }

    fun increaseAge() {
        age++
    }

    fun defineHobby(hobby: String) {
        this.hobby = hobby
    }

    fun displayHobby() = this.hobby

    fun toStringAddon(): String {
        return "Person(name=$name, age=$age, hobby=$hobby)"
    }
}
```

#### with
`with` は、レシーバの拡張関数ではなく、普通の関数です。
そのため、その他のスコープ関数とは呼び出し方が異なっています。

```kotlin
    val foo: Person = Person("with", 20)

    with(foo){
        foo.increaseAge()
        println(foo)
    }
```

#### run
`run` は、ラムダ内ではレシーバの表現を `this` で行います。
そのため、**省略可能**です。
以下では、明示的に記載していますが、冗長なので省略する事が多いです。
また、ラムダの結果(最終行)を返却します。

```kotlin
    val foo: Person = Person("run", 20)

    return foo.run {
        nameToUpperCase()
        println(this)
        this.name
    }
```

#### let
`let` は、run とほぼ同じ動きをしますが、
ラムダ内でのレシーバ表現が `it` です。
そのため、**別名**にする事が可能です。
意味のわかりやすいキーワードに置き換えて、
可読性をあげたりする時に使用します。

```kotlin
    val foo: Person = Person("let", 20)

    return foo.let { it ->
        println(it)
        it.defineHobby("Kotlin")
        println(it.toStringAddon())
        it.displayHobby()
    }
```

#### apply
`apply` は、レシーバ自身を返却します。
そのため、用途としてはレシーバ自身への操作を行う場合で
プロパティの設定や変更などが多い用途です。
レシーバの表現は `this` で行っています。
ここでは、レシーバの関数呼び出しで省略しています。

```kotlin
    val foo: Person = Person("apply", 20)

    foo.apply {
        println(this)
        nameToUpperCase()
        increaseAge()
        println(this)
    }
```

#### also
`also` は、apply のレシーバ表現が `it` となったものと考えてよいです。
そのため、用途としてはレシーバに別名を付けて可読性を上げる必要がある場合になります。

```kotlin
    val foo: Person = Person("also", 20)

    return foo.also { it ->
        println(it)
        it.nameToUpperCase()
        it.increaseAge()
        it.defineHobby("Kotlin")
        println(it.toStringAddon())
    }
```

## まとめ / 振り返り
個人的には、スコープ関数の使い分けは戻り値でレシーバを返したいかどうかだけで判断しています。
あとは、this キーワードが煩雑になりそうな場合は控えるくらいで使い分けを行っています。

![scopefunction-selection.png](https://qiita-image-store.s3.amazonaws.com/0/127983/0e297f02-1bc2-100b-e0c8-e409e03272f9.png)


### 今回のソース
- [shinyay/kotlin-koan](https://github.com/shinyay/kotlin-koan/tree/master/src/main/kotlin/io/pivotal/syanagihara/koan/kotlin/scoping)