他の多くの言語がそうであるように、Kotlinにも中置記法(メソッド名を中に置く記法)に対応したメソッドがいくつか存在します。例えば、
0 until 5 //0..4
mapOf("滝沢カレン" to "カレンの台所") //{滝沢カレン=カレンの台所}
Range型を作るuntil
やPair型を作るto
は中置記法に対応した関数です。
0.until(5)
mapOf("滝沢カレン".to("カレンの台所"))
このように記述しても結果は同じですが、より自然で読みやすいコードにできるのが中置記法の利点です。
また中置記法は特別に選ばれたメソッドでしか使えないわけではなく、ユーザーによって簡単に作り出すことができます。
今回は「中置」という意味の「infix」キーワードを使った「中置記法対応関数」の定義方法について。
個人的には帯が糸井重里ってところがツボ。
infixキーワードを使った関数
それでは中置記法に対応した関数を定義していきましょう。
拡張関数
まずは拡張関数。例としてListの要素をn倍する関数を定義します。
infix fun List<Int>.multiply(n: Int): List<Int>{
return this.map{it * n}
}
定義方法はすごく簡単。頭にinfix
を付ければ、それで中置記法対応関数のできあがり。
multiply
はList<Int>から呼び出し、内部では要素を×nしてListを返します。
この関数は下記のように、通常の関数と同じ呼び出し方法も可能です。
val example = listOf(2,3,4)
println(example.multiply(3)) //[6, 9, 12]
そして中置記法。ドットや()を使わずレシーバ 関数名 引数
で呼び出します。
val example = listOf(2,3,4)
println(example multiply 3) //[6, 9, 12]
クラスメソッド
次にクラスメソッドの定義でinfix
を使ってみましょう。
class Person(private val name: String){
infix fun publish(book: String) = println("${name}は${book}を出版した")
}
val karen = Person("滝沢カレン")
karen publish "カレンの台所" //滝沢カレンはカレンの台所を出版した
基本的には拡張関数と同じ要領で定義したり使用することができます。
クラス内部で使用するときの注意点
クラスメソッドの場合、クラス内の他のメソッドからinfix関数を呼び出す場面があるかもしれません。
その場合、infix関数の呼び出しには「this」が必須であることには注意してください。
class Person(private val name: String){
fun example() = println("")
infix fun publish(book: String) = println("${name}は${book}を出版した")
fun callMethod(some: String){
example() //通常のメソッドは、呼び出し元を省略できる
this publish some
this.publish(some)
}
}
callMethod
から呼び出されるexample
は通常メソッドです。この場合呼び出し元は省略可能、メソッド名だけの表記でOKですが、infix関数のpublish
メソッドは呼び出し元を明記する必要があります。
infix関数 その他の制約
infix関数には上記以外にも、いくつかの制約があります。ここでチェックしておきましょう。
① infix関数は拡張関数かクラスメンバでなければならない
呼び出し元(レシーバ)が無い関数は、中置記法で呼び出せてしまうと関数の左辺が無くなってしまうため、トップレベル関数にはinfix
を使用できません。
② パラメータ(引数)が1つでなければならない
右辺が複数になってしまう可能性がある関数は、中置記法には対応できません。
③ 可変長引数、デフォルト引数を持っていてはならない
2と同じ理由ですが、引数が省略できてしまう場合も不可。
要は呼び出し元と引数が、常に1対1で呼び出せる関数でなければならないということです。
処理の優先度
infix関数は他の演算子と組み合わせて使用する場合も多くあります。組み合わせる演算子によって、処理がどのような順番で行われるのかを確認しておきましょう。
① 算術演算子や型キャスト(as)、rangeTo(..)より優先度が低い
0 until 5 + 7 //「5 + 7」が先に行われるため0..11となる
1 to 5 as Number //型キャストが先に行われ、Pair<Int, Number>となる
1 to 5..8 //処理はrangeToが優先。Pair<Int, IntRange>となる
② 論理演算子(&&/||/and/or/xor)やis/inより優先度が高い
//until2つを最初に処理。以下2つは等価
8 until 10 is IntRange && 5 in 1 until 10 //true
((8 until 10) is IntRange) && (5 in (1 until 10)) //true
標準で用意されているinfix関数
今回の締めとして、Kotlinに標準で用意されているinfix関数の例をいくつかご紹介したいと思います。以下はKotlinのString型、Range型のメソッドです。
Kotlin.String
String型から呼び出せるinfix関数は2種類。
matches
infix fun CharSequence.matches(regex: Regex): Boolean
引数に取った正規表現で、呼び出し元文字列の全文一致検索を行います。
val a = "滝沢カレン"
val b = """\W\Wカレン""".toRegex()
val c = """カレン""".toRegex()
println(a matches b) //true
println(a matches c) //false
zip
infix fun <T, R> Iterable<T>.zip(
other: Iterable<R>
): List<Pair<T, R>>
呼び出し元と引数、各々の要素を1つずつPairにまとめ、それを1要素としたListを返します。
val a = "なんで私にしたのか"
val b = "洗剤を初めて見せた"
println(a zip b)
//[(な, 洗), (ん, 剤), (で, を), (私, 初), (に, め), (し, て), (た, 見), (の, せ), (か, た)]
要素数が異なる場合、結果は文字数の少ない方に合わせた要素数となります。
val r = "なんで私にしたのか"
val b = "洗剤"
println(r zip b) //[(な, 洗), (ん, 剤)]
Package kotlin.ranges
Range型を作るメソッドは中置記法に対応しています。
downTo
infix fun Int.downTo(to: Int): IntProgression
カウントダウンの範囲を作成。引数に取った値まで含まれます。
val r = 10 downTo 0
for(i in r) print(i) //109876543210
上の例はIntですが、Long型やByte型などの整数値、もしくは呼び出し元と引数どちらもChar型なら呼び出し可能。少数値は不可。
10L downTo 5L //OK
10.5 downTo 3.2 //NG
val r = 'u' downTo 'a'
for(i in r) print(i) //utsrqponmlkjihgfedcba
until
infix fun Int.until(to: Int): IntRange
downTo
とは逆に、カウントアップの範囲を作成します。範囲に含まれるのは引数の1つ手前まで。
val r = 0 until 10
for(i in r) print(i)
//0123456789
downTo
と同様、整数型やChar型で利用できます。
step
infix fun IntProgression.step(step: Int): IntProgression
downTo
やuntil
と組み合わせ、指定範囲の刻み幅を指定するのに利用されます。
val r = 10 downTo 0 step 2
for(i in r) print(i) //1086420
参考文献
公式ページ。「Infix notation」に中置記法の記載があります。
なぜか興味をそそられるインタビュー動画。zipメソッドの例はここから。真面目に楽しい。それが滝沢カレン。