[kotlin] 演算子オーバーロードでちょっとハマったこと

とある事情から、kotlinで文字列の掛け算(演算子)を実装したくなりました。

kotlinには演算子オーバーロードの機能があるらしいのでそれを使おうとした時、ちょっとだけハマったので忘れないようにメモ。

環境

Kotlin version 1.3.41-release-150 (JRE 12+33)

やりたいこと

演算子オーバーロードで文字列の掛け算を実装する。

kotlinの演算子オーバーロード

演算子をオーバーロードするときはoperatorキーワードを使って以下のように実装する。

operator fun 関数名:戻り値{
    中身
}

それぞれの演算子と関数名の対応は以下の表の通り

単項演算
演算子関数
+aa.unaryPlus()
-aa.unaryMinus()
a.not()
インクリメント・デクリメント
演算子関数
a++a.inc()
a–a.dec()
二項演算
演算子関数
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.mod(b)
a..ba.rangeTo(b)
a in bb.contains(a)
a !in b!b.contains(a)
a += ba.plusAssign(b)
a -= ba.minusAssign(b)
a *= ba.timesAssign(b)
a /= ba.divAssign(b)
a %= ba.modAssign(b)
添字演算
演算子関数
a[i]a.get(i)
a[i, j]a.get(i, j)
a[i_1, …, i_n]a.get(i_1, …, i_n)
a[i] = ba.set(i, b)
a[i, j] = ba.set(i, j, b)
a[i_1, …, i_n] = ba.set(i_1, …, i_n, b)
比較
演算子関数
a == ba?.equals(b) ?: b === null
a != b!(a?.equals(b) ?: b === null)
a > ba.compareTo(b) > 0
a < ba.compareTo(b) < 0
a >= ba.compareTo(b) >= 0
a <= ba.compareTo(b) <= 0

関数呼び出し

演算子関数
a()a.invoke()
a(i)a.invoke(i)
a(i, j)a.invoke(i, j)
a(i_1, …, i_n)a.invoke(i_1, …, i_n)

実装

kotlinのStringクラスはrepeatメソッドを持っているらしいので、それをラップする形で拡張関数を実装

operator fun String.times(n:Int):String{
    return this.repeat(n)
}

thisはなくてもいけるらしいけど、読みやすさのために書いておきます。

拡張関数

既存のクラスにメソッドを追加するかの様に関数を書けるkotlinの機能

使ってみます。

fun main() {
    var abc = "abc "
    println(abc * 3)
}

# abc abc abc 

普通に掛け算を実装する分にはできました。

掛け算ができるなら複合代入演算*=もできた方が自然です。
というわけで、作ろうと思い実装してみました。

operator fun String.timesAssign(n:Int):String{
    return this.repeat(n)
}

が、コンパイルが通らない…

こんなエラーが出ました。

kotlinc MyString.kt -include-runtime -d bin/sample.jar
MyString.kt:19:7: error: assignment operators ambiguity: 
public operator fun String.times(n: Int): String defined in root package in file MyString.kt
public operator fun String.timesAssign(n: Int): Unit defined in root package in file MyString.kt
    a *= 10
      ^

演算子の解釈が曖昧…ひょっとして

先ほどの複合代入演算子の関数定義を消して複合代入演算を実行。

"a" *= 2
>>> "aa"

あ、出来た。

まとめ

  • 演算対象の型と戻り値の型が同じ場合、複合代入演算は自動で作ってくれるっぽい。

おまけ

演算対象の型と戻り値の型が違っても、定義自体はされるっぽい。

operator fun String.times(n:Int):Int{
     return this.length * n
}

>>> var a = "a"
>>> a * 2
res10: kotlin.Int = 2
>>> a *= 2
error: type mismatch: inferred type is Int but String was expected
a *= 2
^
参考

https://qiita.com/KokiEnomoto/items/2fedf864ff0710927b98