参考にした入門書の内容が薄すぎてトレースする意味がなくなってきた。
いきなり中級書で良かった気がする。
関数
kotlinにおける関数の定義方法。
これもDelphiっぽいけどDelphiの特殊な部分を普通に寄せた感じ。
今思えばDelphiの”関数内の最終評価値が関数の戻り値になる”動きはRubyに通じてるような。
関数の呼び出しに使ったスタックをどう解釈するかの違いだから似たパターンになるんだろう。
1 2 3 4 5 6 7 8 9 10 11 12 |
# kotlin fun getSquare(value: Int): Int { return value * value } println(getSquare(10)) // 100 # Delphi function getSquare(value: Integer): Integer begin result:= value * value end writeln(getSquare(10)); // 100 |
関数の定義において、引数の型を省略することはできない。
以下はN.G.
1 2 3 |
fun getSquare(value): Int { return value * value } |
戻り値が無い関数を定義する際は、戻り値の型をUnit型にする。
なお、関数の戻り値のUnit型のみ省略可能。
1 2 3 4 5 6 |
fun sayHello(msg: String): Unit { println(msg) } fun sayHello(msg: String) { println(msg) } |
よくある省略形として、関数本体が1行しかない場合はブラケットを省略できるけど、
kotlinも省略できる。ただし書き方が少し違う。
コンパイラがワンライナーの型を推測できる場合は、関数の戻り値の型自体を省略できる。
1 2 3 4 |
// 戻り値の型推論なし fun getSquare(value: Int): Int = value * value // 戻り値の型推論あり fun getSquare(value: Int) = value * value |
引数のデフォルト値
よくある引数のデフォルト値定義。普通。
1 2 3 4 |
fun getSquare(value: Int = 10): Int { return value * value } println(getSquare()) // 100 |
デフォルト値ありの引数と無しの引数を混在させることもできる。
必須引数は引数リストの先頭に、省略可能引数は引数リストの後方に置くこと。
1 2 3 4 |
fun getMult(value1: Int, value2: Int = 10): Int { return value1 * value2 } println(getSqure(100)) // 1000 |
引数のデフォルト値にはリテラルだけでなく関数の戻り値を指定することができる。
これは新感覚。
1 2 3 4 5 6 7 |
fun getDefaultValue(): Int { return 15 } fun getMult(value1: Int, value2: Int = getDefaultValue()): Int { return value1 * value2 } println(getMult(100)) //1500 |
引数の名前付き呼び出し
関数の呼び出し時に引数の名前を指定することができる。
ほんと、どっかで見た最近の言語のつまみ食い。
引数の順序に縛られない、引数の順序によらずどの引数も省略できる。
1 2 3 4 |
fun getMult(value1: Int, value2: Int = 10): Int { return value1 * value2 } println(getMult(value1=100, value2=200)) // 20000 |
kotlinはJavaのコードを呼び出せる(はず)だけれども、
Javaのバイトコードは引数の名前を保持していないため、
Javaのメソッドを名前付き引数を使って呼び出すことはできない。
可変長引数
だいたいどの言語にもある可変長引数。
使ったこと無いけど。
他の変数と混在させる場合は、可変長引数を末尾に配置すること。
可変長引数を前に持ってくる場合、呼び出し時にどこまでが可変長引数かわからなくなるので、
可変長引数の後の引数を名前付きで渡す必要がある。(そこまでして順番に拘る必要はないと…)
1 2 3 4 5 6 7 8 |
fun getValues(vararg values: Int): Int { var result = 1 for (value in values) { result *= value } return result } println(getValues(1,2,3,4,5)) // 120 |
可変長引数として配列を渡すことができる。
その際、配列の先頭にスプレット演算子(*)を置く。
ポインタ、参照とは関係なく、
単に配列を可変長引数に展開することから”spread”という名前になっている。
1 2 3 4 5 6 7 8 9 |
fun getValues(vararg values: Int): Int { var result = 1 for (value in values) { result *= value } return result } var ary: intArrayOf(1,2,3,4,5) println(getValues(*ary)) //120 |
スプレッド演算子を使うと、リテラルと配列から1つの可変長引数を作れる。
こんなことしないと思うんだけど。
1 2 3 4 5 6 7 8 9 |
fun getValues(vararg values: Int): Int { var result = 1 for (value in values) { result *= value } return result } var ary: intArrayOf(1,2,3,4,5) println(getValues(10,*ary,10)) //12000 |
複数の戻り値
これは鮮やか。関数が複数の戻り値を返せる。
鮮やかだけども型として常用してはいけない様子。関数の戻り値を受け取る時ぐらいで留めるべき。
以下は、可変長引数で渡した値の算術平均を返す関数。
1 2 3 4 5 6 7 8 9 10 11 12 |
fun getAvarange(vararg values:Int): Pair<Int,Double> { var result = 0 var count = 0.0 for (value in values) { result += value count++ } return Pair(result, result/count) } println(getAvarange(1,2,3,4,5)) // (15,3.0) |
Pairをそれぞれの変数にバラすのは以下の通り。
1 2 3 4 |
val pairs:Pair<Int,Double> = Pair(10,5.0) val (intValue, doubleValue) = pairs println(intValue) // 10 println(doubleValue) // 5.0 |
高階関数
関数の参照を渡すには以下のようにする。
Int型の引数を出力する関数printを、配列のforEachに渡している。
配列のforEachの繰り返しの度にprint関数が評価される。
その際、引数itemとして配列の各要素が渡される。
1 2 3 4 5 |
fun print(item: Int) { println(item) } var ary = arrayOf(1,2,3,4) ary.forEach(::ary) // 1 2 3 4 |
もちろん、関数をラムダ式に置き換えることができる。
ブラケットで囲まれた部分がラムダ式。
arrow(->)の左辺がラムダ式のパラメタ、右辺が関数の本体。
1 2 |
var ary = arrayOf(1,2,3,4) ary.forEach({ num:Int->println(num) }) |
ラムダ式のパラメタの型はコンパイラの型推論で自動的にわかるのなら省略できる。
1 2 |
var ary = arrayOf(1,2,3,4) ary.forEach({ num->println(num) }) |
ラムダ式を与える引数リストにおいて、ラムダ式が最後である場合、
引数リストからラムダ式を除いて以下のようにかける。
1 2 |
var ary = arrayOf(1,2,3,4) ary.forEach(){ num->println(num) } |
さらに、ラムダ式を与える引数リストにおいて、引数がラムダ式しかない場合、
引数リストの空プラケットさえ省略できる。
1 2 |
var ary = arrayOf(1,2,3,4) ary.forEach { num->println(num) } |
そして、ラムダ式に与える引数が一つしかない場合、暗黙の引数itを宣言無しで使える。
1 2 |
var ary = arrayOf(1,2,3,4) ary.forEach { println(it) } |
ラムダ式と匿名関数
あまりラムダ式と匿名関数の違いについて考えたことがなかったのだけども、
ラムダ式の代わりに匿名関数を渡すことができる。同じ意味になる。
ラムダ式は匿名関数の糖衣構文(Syntax sugar)である。
つまり冗長な匿名関数を簡易な書き方で書き直したのがラムダ式である。
下記の通り、匿名関数は冗長。
1 2 |
var ary = arrayOf(1,2,3,4) ary.forEach (fun(num: Int):Unit { println(num) }) |
ラムダ式内でのreturn
Rubyで一つの話題になるラムダ式内でのreturnについて。
kotolinにおいてラムダ式内でreturnすると、該当のラムダ式ではなく直上の関数を抜ける意味になる。
下記はラムダ式を中断するだけでなくary.forEachを包含する関数も中断する。
従って、末尾のprintln(“OK”)は処理されず、OKは出力されない。
1 2 3 4 5 6 |
var ary = arrayOf(1,2,3,4) ary.forEach { if (it>3) return println(it) // 1 2 3 } println("OK") |
ラムダ式のみを中断するにはラベル構文を使用する。
下記のようにloopラベルを書いておくと、ラムダが中断したとき中断の連鎖はloopラベルで終了する。
これは自棄っぱちな感じがするな。Rubyと同等で良いんじゃなかろうか。
1 2 3 4 5 6 |
var ary = arrayOf(1,2,3,4) ary.forEach loop@ { if (it>3) return println(it) // 1 2 3 } println("OK") // OK |
高階関数サンプル
高階関数のサンプル。ある時間がかかる処理を関数として実装し、
高階関数にその関数を渡す。
高階関数側では、その関数の実行前後の時間を計測し経過時間を出力する。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
fun calc(action: ()->Unit): Long { var start = System.currentTimeMillis() action() var end = System.currentTimeMillis() return end - start } val time = calc { var x = 1 for (i in 1..10_000_000) { x++ } } println("処理時間:${time}") |
ローカル関数
関数内で関数を定義することでスコープを限定できる。
内側の関数からは外側の関数のスコープにある変数を参照できる。
1 2 3 4 5 6 7 8 9 |
fun outerFunction() { val constant = 100 fun innerFunction() { var variable = constant * constant println(variable) } innerFunction() } outerFunction() / 10000 |