历时 7 天,我把一万行 Scala 代码移植到了 Kotlin 上!

简介:   【编者按】去年,Google 宣布 Kotlin 正式成为 Android 官方开发语言,由此引发了迁移 Kotlin 的一股热潮。在本文中,作者分享了他在七天内把代码从 Scala 移植到 Kotlin 的经过,以及从中吸取的经验教训。  以下为译文:  上周出了几件事,所以我决定把postgresql-async从Scala移植到Kotlin。虽然现在还有好多缺失的部分,但alpha版已经可以用了在这篇文章中我想分享把代码从Scala移植到Kotlin的经过,以及从中吸取的经验教训,希望可以帮助其他开发者解决同样的问题。而且我也在继续努力,解决剩下的问题。  在Outbra

  【编者按】去年,Google 宣布 Kotlin 正式成为 Android 官方开发语言,由此引发了迁移 Kotlin 的一股热潮。在本文中,作者分享了他在七天内把代码从 Scala 移植到 Kotlin 的经过,以及从中吸取的经验教训。

  以下为译文:

  上周出了几件事,所以我决定把postgresql-async从Scala移植到Kotlin。虽然现在还有好多缺失的部分,但alpha版已经可以用了在这篇文章中我想分享把代码从Scala移植到Kotlin的经过,以及从中吸取的经验教训,希望可以帮助其他开发者解决同样的问题。而且我也在继续努力,解决剩下的问题。

  在Outbrain我转到了一个新的团队,得到的任务之一就是负责将各种模块从Scala 2.10升级到2.11。这个任务是可行的,但十分痛苦,因为许多包都要求我们必须给所有JVM模块“打补丁”,就连Java模块都要!

  由于所有模块都依赖于ob1k-db,而ob1k-db依赖于postgresql-async,后者又依赖于Scala 2.10和2.11下的不同的包。所以,可能更好的做法是干掉所有模块中对Scala的依赖……

  而且上周,在经历了一年多的沉默后,终于有一个提交证实了postgres-sql不再提供文凭维护了。这是压死骆驼的最后一根稻草。

  而且,我们仍然在使用该函数库的MySQL异步风格的版本,而且还没有找到能代替它的东西。

  但一个优势是Scala和Kotlin十分相似,无论是功能还是语法——所以我们很想试试能不能把代码移植过去。

  转换本身包括两个主要步骤:

  自动逐行搜索替换脚本内容,节省一些无谓的打字时间;人工审核代码,修改所有编译错误,决定怎样进行转换,并改进脚本。

  脚本其实是一段非常简单无脑的kscript代码,感觉都没必要贴出来。一些代码行甚至都没有替换成合法的语句(比如模式匹配和类型强制转换的部分)。

  我没有时间也没有能力使用antlr之类的东西去写个语法分析器或完整的转换器,而且我还有一些非常特殊的需求。但你要是有兴趣的话可以试试。

  话不多说,下面是脚本的简化版本:

  1#!/usr/bin/env kscript

  2

  3import java.io.File

  4

  5// usage - one argument a .kt file (Scala file that was only renamed)

  6// or a directory

  7try {

  8 main(args)

  9} catch (e: Exception) {

  10 e.printStackTrace()

  11}

  12

  13fun convert(lines: List): List {

  14 val methodNoBracsRegex=".fun\s+\w+\s+[:=].".toRegex()

  15 val linesWithoutLicense=lines

  16// The below lines just removed license comment

  17// if (lines[0].startsWith("package "))

  18// lines

  19// else

  20// lines.drop(15)

  21 val result=mutableListOf()

  22 linesWithoutLicense.forEach { lineBeforeConv ->

  23 val convertedLine=lineBeforeConv

  24 .replace("extends", ":")

  25 .replace(" def ", " fun ")

  26 .replace("BigInt(", "BigInteger(")

  27 .replace("trait", "interface")

  28 .replace("[", "<")

  29 .replace("]", ">")

  30 .replace("={", " {")

  31 .replace(" new ", " ")

  32 .replace(" Future<", " CompletableFuture<")

  33 .replace(" Promise<", " CompletableFuture<")

  34 .replace(" Array(", " ByteArray(")

  35 .replace(" Array(", " CharArray(")

  36 .replace("with", ",")

  37 .replace("match", "when")

  38 .replace("case class", "data class")

  39 .replace("case _", "else")

  40 .replace("case ", "")

  41 .replace("=>", "->")

  42 .replace(".asInstanceOf<", " as ") //manually fix >

  43 .replace("final ", "")

  44 .replace("fun this(", "constructor(")

  45 .replace(" Seq<", " List<")

  46 .replace(" IndexedSeq<", " List<")

  47 .replace("<:", ":")

  48 when {

  49 convertedLine.startsWith("import ") -> {

  50 val importsLines=if (convertedLine.contains("{")) {

  51 val before=convertedLine.substringBefore("{")

  52 convertedLine.substringAfter("{").substringBefore("}").split(",")

  53 .map { "$before${it.trim()}" }

  54 } else listOf(convertedLine)

  55 importsLines.map { it.replace("_", "*") }.forEach {

  56 result.add(it)

  57 }

  58 }

  59 convertedLine.matches(methodNoBracsRegex) -> {

  60 if (convertedLine.contains(":"))

  61 result.add(convertedLine.replace(":", "():"))

  62 else

  63 result.add(convertedLine.replace("=", "()="))

  64 }

  65 else -> result.add(convertedLine)

  66 }

  67 }

  68 return result

  69}

  70

  71fun main(args: Array) {

  72 val fileName=args[0]

  73 if (fileName.endsWith(".kt")) {

  74 workOnFile(fileName)

  75 } else {

  76 File(fileName).walk().forEach {

  77 if (it.name.endsWith(".kt")) {

  78 workOnFile(it.path)

  79 }

  80 }

  81 }

  82}

  83

  84fun readFileAsLinesUsingReadLines(fileName: String): List=File(fileName).readLines()

  85

  86fun workOnFile(fileName: String) {

  87 if (!fileName.fileExists) {

  88 println("WARN: file not exists $fileName")

  89 return

  90 }

  91 println("working on $fileName")

  92 val lines=readFileAsLinesUsingReadLines(fileName)

  93 val fileContent=convert(lines).joinToString("

  ")

  94 File(fileName).writeText(fileContent)

  95}

  这个脚本是用kscript编写的,它接受一个参数:可以是扩展名已经改为.kt的Scala文件,也可以传递目录,如果是目录则该脚本会递归转换目录中的所有文件。

  这个脚本会进行一些非常简单的逐行查找替换:def替换成fun,trait替换成interface,等等。没什么特别的东西。因为我前面说过,两者语法很相似,这一点起了很大作用。如果转换成Java则可能会更麻烦。

  我写这篇文章的目的就是记录下我做过的事情。一些论坛文件仍然需要转换,同时项目中还有其他人,所以这篇文章会有用的。

  下面的项目顺序不分先后,以后也可能会更新。

  Future → CompletableFuture

  原来的代码大量使用了Scala的Future,所以我需要找个东西来代替。我有许多选择:

  Netty future——似乎语法很复杂,而且已经过时。JavaRX/Guava/其他future库——需要额外的外部依赖。Java 8兼容的Future——至少需要依赖Java 8。Kotlin deferred——主要用于协程(coroutine),所以功能不太多,也不知道与Java用户的兼容性如何,对于我来说有点难度。

  最后决定使用CompletableFuture作为主要的后端库。我觉得没必要在Android中使用响应式的relational-sql库,而且Java 8在Android之外的应用也非常广泛。

  注意,CompletableFuture替换了Scala的Future和Promise。

  依赖

  由于这个项目类似于驱动程序,所以我尽量减少外部函数库的依赖,这个决定也影响了其他的决定。

  Finalize

  貌似在Kotlin中不需要覆盖finalize方法。

  数据结构

  有些我已经忘了,但我记得的转换有以下这些:

  Seq → ListIndexedSeq → ListArrayBuffer → MutableList

  位操作

  Kotlin对于byte的处理有点奇怪,还不支持所有的操作符。一些类我转换成了Java,一些仍然保持Kotlin。希望我处理得没错,因为我并不十分确定Scala怎样处理这些操作。欢迎提意见。

  扩展方法和属性

  我一开始并不太理解,但后来意识到我可以使用扩展(extension)让Kotlin变得跟Scala相似,这一点非常酷。

  例如Kotlin的List中有size,而Scala中叫做length。

  这些问题都可以用扩展解决。

  Try

  我决定从Scala+Arrow移植一个相似的类使用。

  方法定义和调用中的大括号

  Scala并不强制大括号,所以有时转换会很痛苦。

  Duration → Duration

  决定使用java.util.Duration。

  执行上下文和隐含参数

  我发现这个功能非常混乱,所以我把所有隐含参数都改成了必须。虽然代码会变得冗余,但我觉得这样更清晰。

  我使用common pool作为默认的执行上下文,尽管在ob1k中我们使用的是另一个。不管怎样,我们把它也改成了显式传递。

  测试

  原来的库使用了specs2。一开始我想暂时保留Scala的测试,但似乎这样做也需要很多工作,因为许多内部代码都改变了。测试的移植依然在进行中,主要工作都由贡献者们进行。

  Option

  大部分都用nullable的类型替换了,其中用到了一些扩展的帮助函数。

  这里我发现Kotlin的方法更好,因为Scala有时使用Option,有时却直接使用null。

  也可以用Java的Optional替换。

  Version → KotlinVersion

  其中有个专门的逻辑,但这个逻辑似乎很标准,所以我就使用KotlinVersion来替换了。

  隐含转换

  隐含转换是一切的邪恶之源(包括过早优化)。我发现我们的情况中可以很容易地使用扩展方法和Java静态方法来替换隐含转换。比如这里的第25行我们隐含地将ByteBuf转换成了ChannelWrapper,使用的是这里的第25行定义的方法。在Kotlin中,我们在ByteBuf上使用扩展函数,并将ChannelWrapper变成了静态方法。

  Traits → interface + 每个类的委托

  似乎traits只是多重继承的替代品,因为它们有状态。我成功地用类委托(class delegation)替换了它。缺点是这种实现要求方法抛出异常,所以如果没有被重载,那么运行时有可能会出错。见这里的第51行以上,感谢阅读。欢迎大家指正!

目录
相关文章
|
3月前
|
监控 Scala 数据安全/隐私保护
Scala代码实践:软件开发中的如何避免员工接私单的防范
在 Scala 软件开发中防止员工接私单,可以通过实施权限控制和审计日志来限制敏感数据访问,并监测员工行为。使用监控系统检测异常活动,一旦发现可疑行为,自动发送警告和生成报告,以便及时干预。这些措施有助于保护项目进度和质量,提高团队效率。
150 1
|
3月前
|
XML 编译器 Android开发
Kotlin DSL 实战:像 Compose 一样写代码
Kotlin DSL 实战:像 Compose 一样写代码
110 0
|
3月前
|
JSON 监控 数据挖掘
使用Kotlin代码简化局域网监控软件开发流程
使用Kotlin简化局域网监控软件开发,通过获取网络设备的IP和MAC地址,实现实时监控网络流量。示例代码展示了如何创建Kotlin项目,获取网络设备信息,监控网络流量以及进行数据分析和处理。此外,还演示了如何使用HTTP库将数据提交到网站,为网络管理提供高效支持。
123 0
|
17天前
|
安全 Java Android开发
Kotlin字符串秘籍:解锁高效处理与创意应用,让你的代码闪耀不凡!
【8月更文挑战第2天】Kotlin是一门现代化的静态类型语言,以简洁、安全及强互操作性著称,在Android及服务器端开发中广受好评。本文通过与其他语言对比,深入解析Kotlin中字符串的基础和高级用法。Kotlin简化了字符串拼接,支持直接使用`+`操作符,并引入了直观的字符串模板。它提供了丰富的字符串操作函数,如使用索引范围进行子字符串提取,增强了代码的可读性。Kotlin字符串的不可变性提升了程序稳定性。利用扩展函数特性,可以轻松定制字符串行为,提高代码的模块化和重用性。掌握这些技巧能显著提升开发效率和代码质量。
22 1
|
1月前
|
SQL Java 数据处理
实时计算 Flink版产品使用问题之使用MavenShadePlugin进行relocation并遇到只包含了Java代码而未包含Scala代码,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
3月前
|
存储 监控 分布式数据库
Scala代码在局域网监控软件中的分布式处理
该文介绍了如何使用Scala进行局域网监控数据的分布式处理。通过示例展示了利用Scala的并发能力进行数据收集,使用集合操作进行数据处理与分析,以及如何将处理结果存储到分布式数据库(如Cassandra)和自动提交到网站。Scala的并发处理能力和丰富库支持使其在分布式处理中表现出色。
114 3
|
3月前
|
Java Kotlin
java调用kotlin代码编译报错“找不到符号”的问题
java调用kotlin代码编译报错“找不到符号”的问题
152 10
|
10月前
|
缓存 API Android开发
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(下)
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(下)
119 0
|
10月前
|
缓存 Java Kotlin
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(上)
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(上)
83 0