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

第3回 Kotlinを学ぶ

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

Kotlinの関数の定義の方法

まずは簡単な関数を定義する例を示しますリスト7)⁠

リスト7 hello関数

fun hello(name: String) {
  println("Hello, ${name}!")
}

リスト7にhelloという名前の関数を定義しました。関数を定義するにはfunキーワード,関数名,引数リストの順に記述します。引数には型を明示する必要があります。波括弧で関数本体を表します。

関数定義と関数の呼び出し例をリスト8に示します。"Kotlin"を引数に,helloを呼び出しています。

リスト8 関数呼び出し例

fun hello(name: String) {
  println("Hello, ${name}!")
}

fun main(args: Array<String>) {
  hello("Kotlin") // => Hello, Kotlin!
}

値を返す関数

引数を取って,なんらかの計算を施し,その結果を返すような関数を定義してみましょう。シンプルな足し算を行うだけの関数plusリスト9に定義しました。

リスト9 足し算関数を定義

fun plus(a: Int, b: Int): Int {
  return a + b
}

fun main(args: Array<String>) {
  println("2 + 5 = ${plus(2, 5)}")
// => 2 + 5 = 7
}

先ほどのhello関数と違うのは,戻り値の型を指定しているところと,returnキーワードにより値を返しているところです。戻り値の型は,関数の引数リストの直後にコロン:を挟んで記述します。plus関数の戻り値の型は2つのInt型の足し算なのでIntです。returnキーワードは関数の値を返すためのキーワードです。

plus関数は,return文のみにより構成されているので,この場合に限りリスト10のように=を使ったシンプルな記述が可能になります。

リスト10 単一式関数

fun plus(a: Int, b: Int): Int = a + b

関数の基本的な使い方は以上です。リスト11のように面白い関数を作って遊んでみましょう!注4

リスト11 関数で遊ぼう

// 掛け算
fun times(a: Int, b: Int) = a * b

// 平方
fun square(n: Int): Int = times(n, n)

// 大きい方を返す
fun max(a: Int, b: Int): Int = if (a < b) b else a

// 小さい方を返す
fun min(a: Int, b: Int): Int = if (a <= b) a else b

// 最大公約数を返す
fun gcd(a: Int, b: Int): Int {
  var x = max(a, b)
  var y = min(a, b)
  while(y != 0) {
    val w = y
    y = x % y
    x = w
  }
  return x
}
注4)
しれっとwhileが登場していますが,Javaと同様に条件式がtrueの間は繰り返し続ける構文です。

デフォルト引数と名前付き引数

関数の引数にはデフォルト値を設定しておくことができますリスト12)⁠

リスト12 デフォルト引数

fun hello(name: String, exclamation: Boolean = false) {
  val suffix = if (exclamation) "!" else ""
  println("Hello, ${name}${suffix}")
}

リスト12のhello関数は,Boolean型のexclamationという引数を持っていますが,デフォルト値を設定しています。デフォルト値を持った引数(デフォルト引数)は,呼び出しの際に値の指定を省略できます。省略した場合にはデフォルト値が使われるというわけですリスト13)⁠

リスト13 デフォルト引数の関数の使用例

// 第2引数を省略
hello("Kotlin")       // => Hello, Kotlin

// 第2引数を指定
hello("Kotlin", true) // => Hello, Kotlin!

また,関数呼び出しの際に引数へ渡す値を名前指定で渡せますリスト14)⁠

リスト14 名前付き引数

hello(name = "Foo")

// 引数リストの順番に従う必要はない
hello(exclamation = false, name = "Baz")

再帰呼び出し

関数が,自分自身を呼び出すことを再帰呼び出しと言います。再帰呼び出しにより,ループを宣言的に記述できるようになります。たとえば,引数のリストの合計値を返す関数を考えましょう。まずは通常バージョンですリスト15)⁠

リスト15 forによるループ

fun sum(ints: List<Int>): Int {
  var sum = 0
  for (e in ints) {
    sum += e
  }
  return sum
}

forによりループを回しています。変数sumvarにより宣言されており,繰り返し新しい値が代入されています。次に再帰呼び出しのバージョンですリスト16)⁠

リスト16 再帰呼び出しによるループ

fun sum(ints: List<Int>): Int =
    if da(ints.isEmpty()) 0
    else ints.first() + sum(ints.drop(1))

forも再代入もなくなりました。代わりにsum関数の定義の中で自分自身を呼び出しています。

ちなみに,isEmptyfirst()drop(1)は整数のListであるintsのメソッドです。それぞれ,リストが空かどうか,リストの先頭要素,先頭から1つ分要素を除いた新しいリストを返します。

リスト11で定義した最大公約数を求めるgcd関数を再帰呼び出しを使って実装してみましょうリスト17)⁠

リスト17 gcdを再帰関数にする

fun gcd(a: Int, b: Int): Int {
  val x = max(a, b)
  val y = min(a, b)
  return if (y == 0) x
         else gcd(y, x % y)
}

varwhileループも,そして一時変数wも消せました! このように再帰呼び出しを使うとコードがすっきりして読みやすくなることが多いです。

再帰呼び出しの欠点は,関数を何回も呼び出し続けることによるスタックの消費です。何回も(環境によりますが非常に多くの回数)関数を呼び続けるとスタックオーバーフローを起こし,プログラムがクラッシュします。これを回避するためにKotlinには末尾呼び出し最適化(tail call optimization)と呼ばれるしくみが備わっています。

末尾呼び出しとは,再帰呼び出しが末尾にあるような呼び出しです。たとえばリスト17のgcd関数は末尾呼び出しを行っています。このような再帰関数にtailRecursive注5というアノテーションを付けると末尾呼び出し最適化が施され,スタックを食いつぶさないようなコードに展開されますリスト18)⁠

リスト18 末尾呼び出し最適化が効くgcd関数

tailRecursive fun gcd(a: Int, b: Int): Int {
  val x = max(a, b)
  val y = min(a, b)
  return if (y == 0) x
         else gcd(y, x % y)
}

最適化が有効になるのはあくまで末尾呼び出しのみなので,関数によっては再帰の仕方を工夫する必要があります。

注5)
「tail recursive = 末尾 再帰」という意味です。

著者プロフィール

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

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

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

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

コメント

コメントの記入