Kotlinプログラミングを読んで気になった箇所メモ ②

[Kotlinプログラミングを読んで気になった箇所メモ ①](https://qiita.com/yuto-naga/items/2d492db0ea8e1fe1ebb4)の続き
今回は難しかったので5章だけ。

### 5章 無名関数と関数の型

無名関数は名の通り、名前のない関数(メソッド)のこと。
いままで `無名関数 = ラムダ(lambda)式` と思っていたが、ラムダ式は無名関数の**一部**。

`(仮引数) -> {実処理}` みたいな書式がラムダ式。

#### 5.1 無名関数

##### Kotlin標準ライブラリにある組み込み関数を簡単にカスタマイズできるようにする

下記の例では、String型に定義に含まれている `count`関数を使った例。

```kotlin
println("Mississippi".count()) // 11
println("Mississippi".count({ letter -> letter == 's' })) // 4
```

文字列の文字総数を返すcount関数にルールを変えて、 「s」という文字がいくつあるかを数えて返すようにカスタマイズ。

##### 今度は自分で無名関数を定義してみる

下記は1つの変数を定義し、文字列を返却する。

```kotlin
println({
    val today = "2019/01/01"
    "こんにちは、今日は $today です。"
}()) // こんにちは、今日は 2019/01/01 です。
```

`{}` の中に無名関数を定義。
 `{}` の隣に `()` を書くことで、無名関数を実行している。

##### 関数型を定義する

無名関数にも型があり、変数として格納できる。(Functionという型ではない)

```kotlin
  val helloFunction: (String) -> String = { name ->
      val today = "2019/01/01"
      "こんにちは、$name さん。今日は $today です。"
  }
  println(helloFunction("太郎")) // こんにちは、太郎 さん。今日は 2019/01/01 です。
```

`(String) -> String` 

引数を`String`で受け取り、戻り値は`String` にする宣言。

定義した無名関数には `return` キーワードがないのが特徴。
基本的には `return` を書くことを許さない。
これはコンパイラからみて、無名関数自身の戻り値なのか、無名関数を呼び出した関数の戻り値なのか曖昧になるから。

##### itキーワードを使う

`it` を使えば、仮引数の定義が必要なくなる。

```kotlin
val helloFunction: (String) -> String = {
    val today = "2019/01/01"
    "こんにちは、$it さん。今日は $today です。"
}
println(helloFunction("太郎")) // こんにちは、太郎 さん。今日は 2019/01/01 です。
```

上記の例だと、`name ->` が不要になった。
単純な無名関数に使うのはありだが、複雑な関数の場合は別名の定義をしたほうが良いと思う。

count関数も引数がなくても明快で良い。

```kotlin
println("Mississippi".count({ letter -> letter == 's' }))

println("Mississippi".count({ it == 's' }))
```

##### 複数の引数を受け取る

２つ以上のパラメータを定義すると、 `it` は使えないので注意。

```kotlin
val helloFunction: (String, Int) -> String = { name, money ->
    val today = "2019/01/01"
    println("所持金は${money}円です")
    "こんにちは、$name さん。今日は $today です。"
}
println(helloFunction("太郎", 1000))
```

#### 5.2 型推論のサポート

返却型を定義しなくても型推論してくれる。
仮引数がある場合は無名関数の中に入れる。
ただし、無名関数が単純なときだけに使ったほうが良さそう。

```kotlin
val helloFunction = { name: String, money: Int ->
    var today = "2019/01/01"
    println("所持金は${money}円です")
    "こんにちは、$name さん。今日は $today です。"
}
println(helloFunction("太郎", 1000))
```

#### 5.3 「関数を受け取る関数」を定義する

関数の引数はあらゆる型を定義できる。**関数を引数にすることもできる。**

```kotlin
fun main(args: Array<String>) {
    var helloFunction = {...}
    hogeFunction("太郎", helloFunction)
}

// 関数を引数にするFunction
private fun hogeFunction(name: String, helloFunction: (String, Int) -> String) {
    val randomMoney = arrayOf(1000, 2000, 3000).random()
    println(helloFunction(name, randomMoney))
}
```

変数 `helloFunction` は定義しないで以下のようにも書ける

```kotlin
fun main(args: Array<String>) {
    hogeFunction("太郎", { name: String, money: Int ->
        var today = "2019/01/01"
        println("所持金は${money}円です")
        "こんにちは、$name さん。今日は $today です。"
    })
}

// 関数を引数にするFunction
private fun hogeFunction(name: String, helloFunction: (String, Int) -> String) {
    val randomMoney = arrayOf(1000, 2000, 3000).random()
    println(helloFunction(name, randomMoney))
}
```

関数の最後の引数が関数である場合、 **() の外に関数を定義する**ことができる。
つまり、`hogeFunction` の2つ目(最後)の引数が関数なので、以下のように定義が可能。

```kotlin
// ()の外に{}を定義できる。
hogeFunction("太郎") { name: String, money: Int ->
    var today = "2019/01/01"
    println("所持金は${money}円です")
    "こんにちは、$name さん。今日は $today です。"
}
```

#### 5.4 関数のインライン化

ラムダは柔軟にプログラムを書くことができるが、コストがかかる。
ラムダを１つ定義すると、
JVM上に1個のオプジェクトインスタンスが定義され、そのラムダが利用できる **全部の変数** についてメモリ割り当てを実行する。
そのため、ラムダによるメモリのオーバーヘッドが加わり、性能に及ぶ可能性がある。

ラムダを他の関数への引数として使うときは、上記の問題を避ける**インライン化**というオプションがある。

引数に関数(ラムダ)を使う関数に対して `inline` を付与すれば良い。

```kotlin
private inline fun hogeFunction(name: String, helloFunction: (String, Int) -> String) {
    val randomMoney = arrayOf(1000, 2000, 3000).random()
    println(helloFunction(name, ramdomMoney))
}
```

`inline` を付与することで、
コンパイルは jvm上のオブジェクトインスタンスで `hogeFunction()` を呼び出す代わりに、
関数本体を main関数に直接インライン化する。
ただし、状況によってはインライン化できない場合があるので注意。
再帰関数はNG。コンパイルエラーになる

```kotlin
fun main(args: Array<String>) {
    println(sum(5))
}

// 1 ~ nまでの和
private inline fun sum(n: Int): Int {
    if (n <= 0) {
        return n
    }
    return n + sum(n - 1) // コンパイルエラー
}
```

#### 5.5 関数リファレンス(Callable Reference)

関数の引数に関数を提供する方法として、**関数リファレンス**がある。
関数リファレンスは funキーワードで定義した関数(名前付き関数) を引数として渡せる値に変換する。

```kotlin
fun main(args: Array<String>) {
    hogeFunction("太郎", ::helloFunction)
}

private fun hogeFunction(
    name: String, helloFunction: (String, Int) -> String
) {
    val ramdomMoney = arrayOf(1000, 2000, 3000).random()
    println(helloFunction(name, ramdomMoney))
}

private fun helloFunction(name: String, money: Int): String {
    val today = "2019/01/01"
    println("所持金は${money}円です")
    return "こんにちは、$name さん。今日は $today です。"
}
```

変数に渡すこともできる。

```kotlin
val helloFunc = ::helloFunction
```

#### 5.6 戻り値の型としての関数型

戻り値として **関数** を返却することもできる。

```kotlin
fun main(args: Array<String>) {
    hogeFunction("太郎")
}

private inline fun hogeFunction(name: String) {
    val ramdomMoney = arrayOf(1000, 2000, 3000).random()
    val helloFunction = configureHelloFunction(name, ramdomMoney)
    println(helloFunction())
}

// 戻りに関数を返却する関数
private fun configureHelloFunction(name: String, money: Int): () -> String {
    var count = 1
    val today = "2019/01/01"
    return {
        println("この関数の呼び出し回数:${count}回目")
        count++
        println("所持金は${money}円です")
        "こんにちは、$name さん。今日は $today です。"
    }
}
```

`helloFunction` はクロージャになっているため、複数呼び出せば `count` がインクリメントされる。


### 参考文献

- [Kotlinプログラミング](https://www.amazon.co.jp/Kotlin%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-Josh-Skeen/dp/4798160199/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=kotlin%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&qid=1567736071&s=gateway&sr=8-1)
- [Kotlin公式Does](https://kotlinlang.org/docs/reference)
- [Kotlinの「高階関数」と「関数リテラル」の記法が難しすぎる](https://bottoms-programming.com/archives/kotlin-higher-order-function-function-literal.html)
- [再帰関数を理解するための最もシンプルな例](https://qiita.com/jumpyoshim/items/20e6b5e70efa466699b4)