慣れないうちはかなりとっつきにくく、新しい用語もたくさん出てくる無名(匿名)関数やラムダ式。
しかしザックリ言ってしまうと、無名関数は「名前を付けるほどでもない関数」、ラムダ式はその定義構文です。それだけに複雑な処理はほぼ出てきません。
今回は無名関数の基礎となる概念から始めて、関数オブジェクトやラムダ式の構文をまとめていきます。
関数は第一級オブジェクト
無名関数やラムダ式を考えるとき、最も前提にあるのは「(Kotlinの)関数は第一級オブジェクトである」という概念です。ただこれが「そもそも何を言っているのか分からない」という方も多いので、先にこの部分を何となくでも理解することから始めましょう。
第一級オブジェクトとは:
- 変数や定数に代入できる
- 名前が無くてもかまわない
- 関数のパラメータ(引数)として使える
- 関数の戻り値として使える
これが代表的な定義です。例えばInt型の「1」は、第一級オブジェクトです。
var x = 1 //変数に代入できる
fun example(num: Int): Int{ //関数の引数として使える
return num //関数の戻り値として使える
}
println(example(x))
//1
上のコードでは「1」という数値を変数に代入し、それを関数の引数として渡し、関数の戻り値として返すことができています。簡単に言ってしまうと、このような操作ができる値が第一級オブジェクトです。
関数を変数に代入する – 関数オブジェクト
第一級オブジェクトであるということは、この数値「1」と同じように関数も扱えるということになります。
まずは関数を変数に代入してみましょう。
//関数定義
fun callMenu(menu: String): String{
return "今日のメニューは${menu}です"
}
//main関数内
val steak = ::callMenu //関数オブジェクトを変数に代入
println(steak("ステーキ")) //関数呼び出しと標準出力
//今日のメニューはステーキです
ここではcallMenu関数を定義し、それを関数オブジェクトという形で新たな変数に代入しています。こうすることでsteak("ステーキ")
という呼び出しでもcallMenu関数を呼び出せるようになります。
この変数steakに入っているのは、処理的には関数そのものです。
関数オブジェクトの型
では上の例で変数steakの型はどうなっているのか? 型推論を使わずに型を明記すると、こんな形になります。
val steak :(String)-> String = ::callMenu
=
の左辺ではsteakという変数名の後に、()内でその関数が取る引数の型、->の先に戻り値の型が明記されています。これが関数型(中身が関数のオブジェクト)の表記です。この形は後々よく使う形なので覚えておいてください。
ラムダ式による無名関数の定義
上の例ではcallMenuという名前の関数を変数に代入しましたが、どうせ変数に入れてしまうなら名前は不要です。次は名前の無い関数を定義して、それを変数に代入しましょう。
val steak: (String)-> String = {menu-> "今日のメニューは${menu}です"}
println(steak("ステーキ"))
//今日のメニューはステーキです
右辺の波括弧{}
で囲まれた部分は名前の無い関数(無名関数)です。この{}
で囲む記述方法を特にラムダ式と呼びます。
名前を持つ通常の関数と同じように、このラムダ式も関数型として変数に代入することが可能です。
形は違ってもこれは正式な関数。その証拠に、名前付き関数の部品である
- パラメータと戻り値の型指定
- 内部変数
- 処理
の全てがここに含まれていることが分かります。
ラムダ式の書き方
ここからはラムダ式の様々な記述方法やルールを見ていきます。
型推論を使う
まずは上の例の左辺、「明示的な型指定」を省略して型推論を使ってみましょう。
//NG menuの型が指定されていない
val steak = {menu-> "今日のメニューは${menu}です"}
しかしこれはさすがにエラー。型推論を使うことはできますが、その場合はパラメータの型をラムダ式の内部で指定する必要があります。
//OK menuの型をStringと指定する
val steak = {menu: String-> "今日のメニューは${menu}です"}
なお戻り値の型指定は、その型が明確に推論できるなら不要です。
引数が1つなら「it」を使って参照する
ラムダ式に渡す引数が1つのみであれば、ラムダ式ならではの方法でその引数を扱うことができます。
val steak: (String)-> String = {"今日のメニューは${it}です"}
このラムダ式では内部変数が省かれ、{}
の中には処理内容しか記述されていません。引数はどうやって扱うのでしょう?
ここで重要なのが「it」です。Kotlinではラムダ式の引数が1つの場合、「it」をデフォルトの内部変数として利用することができます。
これは頻繁に使う文法なので、ぜひ押さえておきましょう。
パラメータが無いラムダ式
ではパラメータが無い、引数不要の関数はどう書けばよいでしょうか。
val steak = {"今日のメニューはステーキです"}
そのままですね。処理だけを記述すればOKです。型を明記するとこうなります。
val steak: ()-> String = {"今日のメニューはステーキです"}
複数行のラムダ式
複数行の処理も普通に記述できます。
val steak = {arg: String->
val str = "ステーキ"
val num = 101
"今日の${arg}は${str+num}枚です"
}
ラムダ式内ではreturn禁止?
ラムダ式には関数に付きもののreturnがありません。
「値を返す」という意味でのreturnはラムダ式には不要です。というかラムダ式内のreturnは、ループ処理を抜けるといったようなケースを除き、コンパイラから許されていません。
ラムダ式では最後の行が自動的に戻り値となります。もしreturnを使って値を返したいという場合、通常の無名関数の定義を使いましょう。
ラムダ式を使わない無名関数の定義
ラムダ式 = 無名関数ですが、全ての無名関数 = ラムダ式ではありません。こちらではreturnを明示して、無名関数を定義してみたいと思います。
val steak = fun (): String{return "今日のメニューはステーキです"}
といってもこの構文は、通常の関数定義構文から関数名を省いただけです。単一式関数であれば、
val steak = fun (): String = "今日のメニューはステーキです"
このように記述してもかまいません。
こうすることによって、returnを使って明示的に戻り値を指定することはできますが、正直あまり見る機会も少ないので軽く紹介するだけにしておきます。
おさらい
今回は無名関数の入り口ということで、
- 第一級オブジェクトの概念
- 関数オブジェクト、関数型という型
- 無名関数の定義構文であるラムダ式の記述方法
- ラムダ式のitキーワード
- ラムダ式を使わない無名関数
についてお話しました。色々と形が変わってしまうために、初心者には非常につかみにくいラムダ式ですが、なんとなくでも理解の助けになればと思います。