プログラマに優しい現実指向JVM言語 Kotlin入門

第3回 Kotlinを学ぶ

この記事を読むのに必要な時間:およそ 7 分

関数オブジェクトと関数型

Kotlinでは関数をほかの値と同じように変数に代入したり,引数として関数に渡したり,戻り値として受け取ったりできます。このようにほかの値と同様の形になった関数を,便宜的に関数オブジェクトと呼ぶことにします。実際に関数を変数に代入する例をリスト19に示します。

リスト19 関数オブジェクト

fun succ(n: Int) = n + 1
val hoge = ::succ

定義されたsucc関数を変数hogeに代入しました。ポイントは,関数名の直前に::と記述することです。::を置くことで関数オブジェクトを得ることができるのです。

関数オブジェクトの関数としての機能を呼び出すには,関数オブジェクトのinvokeメソッドを使いますリスト20)⁠

リスト20 invokeメソッド

val r = hoge.invoke(5)
println(r) // => 6

この機能はよく使うので構文糖衣が提供されています。リスト21のように普通の関数呼び出しに似ています。

リスト21 invoke呼び出しの構文糖衣

val r = hoge(5)
println(r) // => 6

ところで,変数hogeの型を明示していませんでしたが,関数オブジェクトの型はどのようになるのでしょうか。hogeの宣言を型推論に頼らないで記述するとリスト22のようになります。

リスト22 関数オブジェクト

val hoge: (Int) -> Int = ::succ

(Int) -> Intの部分が関数の型です。->を挟んで左が引数の型リスト,右が戻り値の型を表現しています。2つの引数を取る関数の型は,たとえば(Char, Int) -> Stringのようになります。

高階関数

関数オブジェクトと関数型についてわかったので,関数オブジェクトを引数に取る関数について解説します。関数を引数に取ったり,関数を返すような関数のことを高階関数(こうかいかんすう:higher-order function)と呼びます。簡単な例をリスト23に示します。

リスト23 高階関数の例

fun apply(n: Int, f: (Int) -> Int): Int {
  println("開始")
  val r = f(n)
  println("終了")
  return r
}

apply関数は引数を2つ取ります。Int型のnと,(Int) -> Int(すなわち関数型)fです。apply関数の動きとしては,nを引数にfを適用した結果を返すだけです。では,このapply関数を使ってみましょうリスト24)⁠

リスト24 apply関数を使う

val got = apply(5, ::succ)
println(got) // => 6

iとして5を,fとしてリスト19で定義したsucc関数の関数オブジェクトを渡しています。このコードを実行すると「開始」⁠終了」⁠6」と各行に表示されます。

もう少し複雑で役に立ちそうな例を見てみましょう。リスト25で定義したmap関数は,リストの各要素を変換して新しいリストを得る関数です。

リスト25 リストの各要素を変換して新しいリストを得るmap関数

fun map(ints: List<Int>, f: (Int) -> Int): List<Int> {
  val newList = java.util.ArrayList<Int>()
  for (e in ints) {
    newList.add(f(e))
  }
  return newList
}

関数シグネチャを注意深く見てみましょう。map関数は2つの引数を取ります。IntListであるintsが第1引数で,元となるリストです。(Int) -> Int型のfは,リストの要素に適用される変換ロジックです。そして,戻り値の型はList<Int>で,これが変換後のリストとなるわけです。

次に関数本体です。新しいリストが欲しいので,リスト(ここではjava.util.ArrayListのインスタンスを生成します。生成したリストにnewListという名前を付けておきます。ここでforループが登場し,intsの各要素に対してループします。各要素はfの引数となり,その適用の結果得られた値がnewListに追加されていきます。こうしてnewListは元のリストの各要素が変換された値で構成されるリストとなり,mapの戻り値となります。

実際にmapを使ってみましょうリスト26)⁠

リスト26 map関数を使う

// [2, 3, 4]のリストを作る
val src = listOf(2, 3, 4)

// 平方を得る関数を各要素に適用
fun square(n: Int): Int = n * n
println(map(src, ::square)) // => [4, 9, 16]

// 階乗を得る関数を各要素に適用
fun factorial(n: Int): Int =
    if (n == 1) 1
    else n * factorial(n - 1)
println(map(src, ::factorial)) // => [2, 6, 24]

このように,高階関数の利点は抽象的な部品であるということです。例に示したmap関数はリストの各要素を別の値に変換する処理ですが,変換の詳細には触れていません。変換の詳細は,引数fに任されているのです。部品が抽象的であるということは,コードの再利用をより容易にすることを意味します。

著者プロフィール

長澤太郎(ながさわたろう)

早稲田大学情報理工学科を2012年に卒業。同年入社したメーカー系SIerを経て,2013年にエムスリー株式会社へ入社。以来,世界の医療を変革するためソフトウェアエンジニアとして従事。

日本Kotlinユーザグループ代表,日本Javaユーザグループ幹事を務める。国内初となるKotlin入門書「Kotlinスタートブック」(リックテレコム)の著者。

ビールとディズニーが大好き。

コメント

コメントの記入