Help us understand the problem. What is going on with this article?

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

Kotlinプログラミングを読んで気になった箇所メモ ①の続き
今回は難しかったので5章だけ。

5章 無名関数と関数の型

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

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

5.1 無名関数

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

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

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

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

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

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

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

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

関数型を定義する

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

  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 を使えば、仮引数の定義が必要なくなる。

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

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

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

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

println("Mississippi".count({ it == 's' }))
複数の引数を受け取る

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

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

5.2 型推論のサポート

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

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

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

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

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 は定義しないで以下のようにも書ける

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つ目(最後)の引数が関数なので、以下のように定義が可能。

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

5.4 関数のインライン化

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

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

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

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。コンパイルエラーになる

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キーワードで定義した関数(名前付き関数) を引数として渡せる値に変換する。

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 です。"
}

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

val helloFunc = ::helloFunction

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

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

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 がインクリメントされる。

参考文献

yuto-naga
備忘録用。 基本的に内容が浅いことしか書きません。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away