largest public Minecraft servers in Japan Minecraftを拡張し、プレーヤーが⼤量にブロックを破壊できるように The game is tweaked; players can break a lot of blocks (Seichi stands for grading, levelling the ground)
growing system had become too complex; bugfix was very difficult 機能開発をほぼ⽌めてリファクタリング/再実装に注⼒しようという話になった のが2018年初頭 The beginning of 2018 was when the team decided to concentrate on refactoring / reimplementation rather than adding new features
to Kotlin from Java is easy IntelliJ IDEAに⼊っているJava -> Kotlinのコンバータの精度がとても良い IntelliJ IDEA provides a very accurate Java-to-Kotlin converter Java -> Scalaのコンバータは割と動かないコードを吐いた Java-to-Scala converter by IntelliJ often yielded code that doesn't compile チームでKotlinの⽅が書ける⼈が多かった The dev team was more comfortable with Kotlin than with Scala
We converted several .java s to .kt , formatting or cleaning them along the way. But it seemed we had to lean towards purely functional programming to simplify internal states...
developers was unknown to me Kotlinでの純粋関数型プログラミングを⼿助けするライブラリがあるが、仕組 みを質問され完全に答えられる程度になるのに⾃分も時間が掛かりそう I thought it'd take a lot for me to be able to understand the internals of libraries supporting purely functional programming in Kotlin
same project コンパイル順に依存関係があり、Java + Kotlin + Scalaを同時にコンパイルでき ない There is an internal dependency in the compilation. We cannot compile Java + Kotlin + Scala at the same time KotlinとScalaの⽂法はとても似ている Kotlin and Scala are very similar in syntax
complex. Factoring out scala to a subproject was very difficult, if not infeasible. ⼀括でやるしかなさそう It seemed like doing everything in one shot was the only option ネタバレをすると、14⽇間⼀度もコンパイルは通らなかった Spoiler alert: In fact, the source could not be compiled for 14 days
that has trivial Scala counterpart can be replaced in the whole project R-Click /src -> Replace in Path on IDEA とはいっても別⾔語。この置換は単純なものが多いとはいえ慎重に正規表現を 組む必要はある。 Kotlin and Scala are two different languages. We need to carefully design regexp to perform project-wide replacement!
) class ([A-Za-z]+(\[.*\])?(\s*(protected|private)\s*)?(\(.*\))?\s*)\:(\s*?\S+) to class $1 extends $6 -class BungeeReceiver(private val plugin: SeichiAssist) : PluginMessageListener { +class BungeeReceiver(private val plugin: SeichiAssist) extends PluginMessageListener { (スペースが余分に⼊っているがlintで後で消すのでこういうのは無視 Extra spaces around extends will eventually be eliminated by the linter)
List (list constructor) def (\[[^\]]*\]) ([^\(]*) to def $2$1 (generic function definition) object (\S+)\s*:\s*(\S+) to object $1 extends $2 (object extends) \)\: ([A-Z]\S+) \{ to \)\: $1 = \{ (Scala's method is a single expression) ([A-Za-z)])!! to $1 (ignore assert-non-null) as ([A-Z]\w+) to \.asInstanceOf\[$1\] (downcasts) typealias to type (type aliases) その他⼩さな全体置換 and other minor syntactic replacements
until all launched coroutines are finished fun main() = runBlocking { launch { doWorld() } println("Hello,") } suspend fun doWorld() { // delay is another suspend fun // the execution "pauses" before this call delay(1000L) // the execution continues ... println("World!") } (adopted from Kotlin Programming Language, https://kotlinlang.org/docs/reference/coroutines/basics.html)
は実は Continuation[R] を取る普通の関数 Kotlin's suspend fun ...(): R is actually an ordinary function that takes Continuation[R] as an extra argument 最初はシグネチャを変えて回っていたが、むしろエラーが増えて⾒通しが悪く なりそうということで @SuspendingMethod アノテーションを作り、 suspend def -> \@SuspendingMethod def と置換した At the beginning I was changing the signatures to take the extra parameter. This turns out to just increase errors, so I decided to fabricate a @SuspendingMethod annotation and applied suspend def -> \@SuspendingMethod def .
scopedFunction(f: ExistingType.() -> Unit): Unit { ... } // scoped function fun ExistingType.extfun(): Int { ... } // extfun implicit class を使った enrich-my-library パターンで解決 Can be resolved using enrich-my-library pattern through implicit class es
convenient operations to manipulate null s val nullableProperty: Int? = nullableValue?.property // safe calls val result = nullableExpression ?: return -1 // elvis operator
class を⽤意することで解決する Wrapping nullables in Option is a way. Having generic implicit class eliminates needs for ?: object Nullability { implicit class NullabilityExtensionReceiver[T](val receiver: T) extends AnyVal { def ifNull(f: => T): T = if (receiver == null) f else receiver } } import {...}.Nullability._ val result = nullableExpression.ifNull { return -1 }
E.setSomething といったメソッド を E.something や E.something = ... とアクセスできるプロパティにラップする機 能がある Kotlin has a feature to wrap get ters and set ters defined in Java class as properties. public final class SomeClass { private int field = 1; public SomeClass() { ... } public int getField() { return field; } public void setField(int newValue) { field = newValue } } someClassValue.field = someClassValue.field + 1
feature feels very ergonomic when interacting with Java class from Kotlin. SeichiAssist had been extensively utilizing this getter/setter-to-property conversion.
direct assignment to a field is indistinguishable from an assignment to Kotlin-generated property based on a setter Getterに関しても同じ (Scalaでの .getPlayer はKotlinでは .player に⾒える) The same goes for getters; .player in Kotlin looks like .getPlayer in Scala.
はプロパティとして定義していなかった から、直後に = が来ていない時点でこれがすぐにgetter呼び出しだとわかる It is often possible to affirm that a certain property calls are definitely getter calls. For example, .onlinePlayers was never defined as a property. No = implies this is a getter access! Now we can employ the POWER of RegExp .onlinePlayers(?<! ?=) to .getOnlinePlayers .onlinePlayers(?<= ?=)(.*) to .setOnlinePlayers($1)
60 or 70 percent of effort went here. It is not impossible to implement an automatic translation... but my judge was that it is faster to do everything by force. I still think I was right.
感じになる場合がある When the syntactic concepts don't agree, using some feature in the target language or annotation may resolve the translation issue 元⾔語の⼀つの構⽂がターゲット⾔語で⼆つの機能に分かれる場合つらい。頑 張るしかない。 When an unified syntax in the original language corresponds to two different syntaxes in the target language, that is going to be a big problem