Kotlinのソースコードで稀に出会う.()
。
ドットと括弧が並ぶ記法です。
似た形で、他クラスのメソッドを呼び出す場面で使われるインスタンス.メソッド名()
はよく見かけますが、.()
にはメソッド名がない...?
ならばこれは何なのか、ということで今回はKotlinの.()
について調べました。
結論
.()
は、レシーバを持つ関数型を表すために使われる記法の一部です。
つまり、.()
自体は型を表す一部で、実際にメソッドを呼び出すような役割はありません。
関数型を指定する部分、高階関数や関数リテラルを記述する際に使用されます。
詳しく見る
.()の意味
.()
は「レシーバを持つ関数型を表すために使われる記法の一部」です。
ここで「レシーバを持つ関数型」をより噛み砕いてみます。
-
レシーバとは
あるメソッドをそれから受け取ることができるオブジェクト、つまり、そのメソッドを提供するインスタンスを指します。
馴染みがないと難しく考えてしまいますが、例えば、他クラスのメソッドを呼び出す場面で使われるインスタンス.メソッド名()
のインスタンス部分もレシーバです。 -
関数型とは
そのメソッドの引数や戻り値の対応を示す型のことです。
型は、文字列を示すString型や数値を示すInt型、Long型と同じ意味の型です。
例えばInt型の引数を2つ受け取ってString型の戻り値を返すメソッドなら、関数型は(Int, Int) -> String
となります。
したがって、「レシーバを持つ関数型」とは、
①そのメソッドを提供するインスタンス、②メソッドの引数、③メソッドの戻り値
の3つの型をセットで表したものと言えます。
.()
はレシーバを持つ関数型を表すために使われる記法の一部であり、記型全体は以下のようになっています。
レシーバの型 .(引数の型) -> 戻り値の型
括弧内で指定した引数を元にレシーバを呼び出し、戻り値を返す関数を表しています。
そして、Kotlinで型を明示する必要のある場所といえば、メソッドの引数や戻り値、変数などの定義部分です。
そこに関数型が書かれる場合、つまりメソッドの引数として関数を受け取ったり、変数に関数を定義したりする場合に、.()
が使われます。
引数に関数をとるメソッドについては、高階関数とラムダをご覧ください。
.()の使い方
レシーバを持つ関数型を引数にとる高階関数を定義し、実際の定義方法や呼び出し方法について確認します。
今回はmainメソッドから呼ばれる高階関数decidedFeedを実装しました。
decidedFeedは、numという関数型の引数をとります。
全体像は以下です。
class Cat{
//①高階関数を定義
fun decidedFeed(num: Int.(String) -> String){
//②引数に指定された関数を用いて処理を実施
println(3.num("カリカリ")) //ミケの餌は3番のカリカリ
}
}
fun main() {
//レシーバのインスタンスを生成
val cat = Cat()
//③高階関数を呼び出す
cat.decidedFeed { feed -> "ミケの餌は${this}番の$feed" }
}
使われたオブジェクトの種類やゆるい解説については下記をご覧ください。
-
高階関数を定義する
高階関数の定義部分では、通常のメソッドと同じように、メソッド名と引数の型を指定します。
今回は引数の型に「レシーバを持つ関数型」を用いることで.()
を使用しました。
ここで指定した型を元に、②引数numの使用部分、③高階関数decidedFeedの呼び出し口を定義します。 -
引数に指定された関数を用いて処理を実施
高階関数decidedFeedの中では、引数として渡された関数numを実行し、その戻り値Stringをprintlnで表示するようにしました。
引数として渡される関数を実行する際は、レシーバオブジェクト +.
+ 引数名、続く括弧内で引数として指定された関数numの引数を指定します。
今回は①で指定したnum: Int.(String) -> String
の型に則り、レシーバのIntと引数のStringを用いて、戻り値Stringを返す処理を記述しました。
高階関数decidedFeedはCatクラスに定義されていますので、Catクラスのインスタンスを元に呼び出します。
通常はメソッド名(引数)
の形で呼ばれますが、Kotlinでは、引数の最後が関数の場合にその関数を(引数)
の外に出して{ }
で囲っていて良いという決まりがあるため、今回はさらに不要な()を削除し、ラムダ式のブロックを用いて引数を定義しています。
また、引数の{ feed -> "ミケの餌は${this}番の$feed" }
では、ここで新たに定義される、feedという高階関数decidedFeedの引数に指定されたnumの引数のオブジェクトと、レシーバオブジェクトのthisを使用しています。
num部分、今回の引数のラムダ式部分ではレシーバオブジェクトが暗黙的にthisになります。そのため、thisを用いることで、レシーバオブジェクトにアクセスすることができます。
今回は①のnum定義部分で.()
直前に記載された、Int型のオブジェクトがthisとなります。
以上、.()
を使用した高階関数の例でした。
実は、レシーバを持つ関数型は以下のように.()
を使用せずとも表せるという背景があり、.()
自体はあまり見ない形かもしれません。
(A, B) -> C
⇄ A.(B) -> C
個人的には(A, B) -> C
の方が分かりやすいと感じますが、A.(B) -> C
の形が出てきたときにも落ち着いて対応していきましょう。
最後までお読みくださりありがとうございました。