[Kotlin]「infix」で中置記法に対応した関数を作る

他の多くの言語がそうであるように、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を返します。

[Kotlin]Extension入門 - 拡張関数と拡張プロパティを定義する
あんなこといいな♪ できたらいいな♪ あんな型こんな型いっぱいあるけど~♪ 「は...

この関数は下記のように、通常の関数と同じ呼び出し方法も可能です。

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

downTountilと組み合わせ、指定範囲の刻み幅を指定するのに利用されます。

val r = 10 downTo 0 step 2
for(i in r) print(i)     //1086420

参考文献

公式ページ。「Infix notation」に中置記法の記載があります。

https://kotlinlang.org/docs/reference/functions.html

なぜか興味をそそられるインタビュー動画。zipメソッドの例はここから。真面目に楽しい。それが滝沢カレン。

YouTube
作成した動画を友だち、家族、世界中の人たちと共有
タイトルとURLをコピーしました