Kotlin スコープ関数 用途まとめ

## 結論（2021-07-06更新）

### 個人的なスタンス

個人的な意見として、原則 nullableのために`let`を使うのみに留めたいところです。
そもそも、このような記事を書いて、それなりに閲覧されている程度には、スコープ関数は難しいからです。
JavaのAPIを使う場合、`also`や`apply`が便利な場面があって、これは例外的に使ってもいいかもしれません。
が、スコープ関数を使わない素直なコードの選択肢を常に持っておくべきでしょう。

### 一応 スコープ関数の図解

![Kotlinの概要再確認と2018年の使い方.png](https://qiita-image-store.s3.amazonaws.com/0/15731/3d2b0462-d227-77cb-1a7c-23a10e7b8469.png)

![Kotlinの概要再確認と2018年の使い方 (1).png](https://qiita-image-store.s3.amazonaws.com/0/15731/4ffc053c-98b5-bad6-37d9-5d8dc826ab2b.png)



**--------以下、詳しく知りたい人向け--------**

Kotlinの標準ライブラリには「スコープ関数」と呼ばれる4つの関数があります。
`let`、`with`、`run`、`apply`です。
それぞれ似ているので使い分けが難しいと思い、私なりの考えをまとめておきます。

（2017-01-26 追記） ver1.1で追加される`also`について解説を追加しました。
（2017-06-08 追記) 「スコープ関数」という名称の由来について追記しました。

## let

### 定義

```kotlin
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
```

### 使用例

```kotlin
val s = "hoge".let { it.toUpperCase() }
println(s) //=> HOGE
```

定義からもわかりますが`let`は任意の型の拡張関数です。
上記の例では`String`インスタンス`"hoge"`をレシーバとしています。
`let`は引数として関数を取ります。
この関数は`let`のレシーバを受け取り、任意の型を返します。

### 主な用途

nullableな変数に対して使うことが多いと思います。
nullableと組み合わせて使うことにより`let`はJavaの`Optional`における次の3つのメソッドの役割と似ています。

* `map`
* `flatMap`
* `ifPresent`

例えば`String?`な変数`foo`の大文字表現を得たい場合にはこう記述できます。

```kotlin
val upperCase: String? = foo?.let { it.toUpperCase() }
```

`foo`が`null`である場合には`?.`呼び出しにより`let`を実行せず`null`を返します。
`foo`が`null`でない場合には`let`が実行され`it.toUpperCase()`により`foo`の大文字表現が返されます。

蛇足ですが、上記のようなケースでは`foo?.let(String::toUpperCase)`と記述できます。

## with

### 定義

```kotlin
public inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
```

### 使用例

```kotlin
val s = with("hoge") { this.toUpperCase() }
println(s) //=> HOGE
```

`with`は`let`とは異なり拡張関数ではありません。
第一引数に任意の型`T`を取ります。
第二引数に関数を取りますが、`T`をレシーバとするメソッドである必要があります。
上記の例における`{ this.toUpperCase() }`の`this`は`"hoge"`を指します。
もちろん`this`は省略可能なので`{ toUpperCase() }`と記述してもOKです。

### 主な用途

あるインスタンスに対する複数の操作をすっきり記述することが目的だと思われます。
例えばインスタンスを生成し、各種設定を行ってから実際に使用するという場面は多いです。

```kotlin
val frame: JFrame = with(JFrame("My App")) {
  size = Dimension(600, 400)
  defaultCloseOperation = JFrame.EXIT_ON_CLOSE
  setVisible(true)
  this
}
```

`size`プロパティや`setVisible`メソッドは`JFrame`のメンバです。
つまりこの文脈でレシーバは`this`となり、省略可能なのでレシーバを繰り返し記述する必要がなくなります。

この用途のために`let`を使うことは適していません。
なぜなら`size`や`setVisible`にアクセスする際にはレシーバとして`it`が必須であり`it.size`、`it.setVisible(true)`のように記述する必要があるからです。

上記の例のように`with`の引数として渡したインスタンスをごにょごにょした後に、そのインスタンス自体を返す場合は後述の`apply`の方が適しているかも知れません。

## run

### 定義

```kotlin
public inline fun <T, R> T.run(f: T.() -> R): R = f()
```

### 使用例

```kotlin
val s = "hoge".run { toUpperCase() }
println(s) //=> HOGE
```

`let`と`with`が合わさったような定義になっています。
`run`は任意の型`T`の拡張関数で、その`T`をレシーバとするメソッドのような関数を引数に取ります。

### 主な用途

用途も`let`と`with`の合体版だと思います。

```kotlin
val frame: JFrame? = frameOrNull()
frame?.run {
  size = Dimension(600, 400)
  defaultCloseOperation = JFrame.EXIT_ON_CLOSE
  setVisible(true)
}
```

## apply

### 定義

```kotlin
public inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
```

### 使用例

```kotlin
val s = "hoge".apply { toUpperCase() }
println(s) //=> hoge
```

今回は他の使用例と変わり、変数`s`は小文字のままです。
`apply`は引数として受け取った関数を実行して何か仕事をしますが、返す値はレシーバだからです。

### 主な用途

`with`の、レシーバを返して欲しいときに使う版と言えます。
`with`を使用したコードと見比べてください。

```kotlin
val frame: JFrame = JFrame("My App").apply {
  size = Dimension(600, 400)
  defaultCloseOperation = JFrame.EXIT_ON_CLOSE
  setVisible(true)
}
```

`with`を使用したバージョンでは、引数の関数内の最後に戻り値として`this`と明記していました。
`apply`はレシーバを返すので`this`と明記する手間が省けます。

Android開発者にはおなじみだと思いますが、
`Fragment`をnewして、argumentsをセットするパターンを実装する際に効果を発揮します。

```kotlin:apply未使用
class MyFragment: Fragment() {
  companion object {
    fun new(foo: Int, bar: Int): MyFragment {
      val args = Bundle()
      args.putInt("foo", foo)
      args.putInt("bar", bar)
      val myFragment = MyFragment()
      myFragment.arguments = args
      return myFragment
    }
  }
}
```

```kotlin:apply使用
class MyFragment: Fragment() {
  companion object {
    fun new(foo: Int, bar: Int): MyFragment =
      MyFragment().apply {
        arguments = Bundle().apply {
          putInt("foo", foo)
          putInt("bar", bar)
        }
      }
  }
}
```

## 実際のプロダクトでは

手元のKotlinソースコードが7000行ほどのAndroidアプリで、どの程度スコープ関数が使われているか調べてみました。

|関数|使用回数|
|---|---|
|let|54|
|with|0|
|run|40|
|apply|37|

`with`は1回も使ってませんが、それ以外は、けっこう使ってるんですね。
スコープ関数の便利さをお伝えできれば嬉しいです。

## also

**ver 1.1**から標準ライブラリに加わる拡張関数です。

### 定義　

```kotlin
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
```

### 使用例と主な用途

```kotlin:単純な例
val s = "hoge".also { it.toUpperCase() }
println(s) //=> hoge
```

定義や上記の例からもわかるように、`apply`とほぼ同じです。異なる点は「引数が元のレシーバ（`"hoge"`）の拡張関数ではなく、元のレシーバを引数に取る関数である」というところです。その利点は2つあります。
まず、名前を付けられることです。引数の関数をラムダ式として記述した場合、元のレシーバを`it`として参照できますが、何か意味のある名前を付けてやれば（例えば`name`とか`label`とか）、コードの可読性が上がるでしょう。
そしてもう一つの利点は、ラムダ式の内と外で`this`の意味が変わらないということです。

```kotlin:applyを使用
val button = Button(this).apply {
  text = "Click me"
  setOnClickListener { 
    startActivity(Intent(this@MainActivity, NextActivity::class.java))
    // 単なる「this」ではNG   ^
  }
}
```

```kotlin:alsoを使用
val button = Button(this).also { button -> 
  button.text = "Click me"
  button.setOnClickListener { 
    startActivity(Intent(this, NextActivity::class.java))
  }
}
```

同じく「元のレシーバを引数に取る関数」を引数に取る`let`との違いは、`let`は戻り値を自分で決めるのに対し、`also`は元のレシーバが返されるという点です。

```kotlin:letを使用
val button = Button(this).let { button -> 
  button.text = "Click me"
  button.setOnClickListener { 
    startActivity(Intent(this, NextActivity::class.java))
  }
  button // letの場合はこれが必要になる
}
```

## 「スコープ関数」という名称について

Kotlin公式ブログの下記エントリにて使用されている「scope functions」が由来です。

[What’s new in Standard Library M13 and M14](https://blog.jetbrains.com/kotlin/2015/09/whats-new-in-standard-library-m13-and-m14/)

引用すると

> Prior to M13 there were two so-called scope functions in the Standard Library: let and with. We call 
them scope functions, because their only purpose is to modify scope of a function passed as the last 
parameter.

要するに、`let`とかの関数の目的は、引数に取る関数のスコープを変更することだから「scope functions」と呼んでいると。