1. 程式人生 > >歷時 7 天,我把一萬行 Scala 程式碼移植到了 Kotlin 上!

歷時 7 天,我把一萬行 Scala 程式碼移植到了 Kotlin 上!

640?wx_fmt=gif

【CSDN編者按】去年,Google 宣佈 Kotlin 正式成為 Android 官方開發語言,由此引發了遷移 Kotlin 的一股熱潮。在本文中,作者分享了他在七天內把程式碼從 Scala 移植到 Kotlin 的經過,以及從中吸取的經驗教訓。

640?wx_fmt=jpeg

以下為譯文:

上週出了幾件事,所以我決定把postgresql-async從Scala移植到Kotlin。雖然現在還有好多缺失的部分,但alpha版已經可以用了在這篇文章中我想分享把程式碼從Scala移植到Kotlin的經過,以及從中吸取的經驗教訓,希望可以幫助其他開發者解決同樣的問題。而且我也在繼續努力,解決剩下的問題。


640?wx_fmt=png

首先我想解釋一下為什麼要移植?


在Outbrain我轉到了一個新的團隊,得到的任務之一就是負責將各種模組從Scala 2.10升級到2.11。這個任務是可行的,但十分痛苦,因為許多包都要求我們必須給所有JVM模組“打補丁”,就連Java模組都要!

由於所有模組都依賴於ob1k-db,而ob1k-db依賴於postgresql-async,後者又依賴於Scala 2.10和2.11下的不同的包。所以,可能更好的做法是幹掉所有模組中對Scala的依賴……

而且上週,在經歷了一年多的沉默後,終於有一個提交證實了postgres-sql不再提供維護了(https://github.com/mauricio/postgresql-async/commit/5716ac43818b6be0dc4fcc2b2655dde3411cdbe0)。這是壓死駱駝的最後一根稻草。

而且,我們仍然在使用該函式庫的MySQL非同步風格的版本,而且還沒有找到能代替它的東西。

但一個優勢是Scala和Kotlin十分相似,無論是功能還是語法——所以我們很想試試能不能把程式碼移植過去。


640?wx_fmt=png

怎麼做?


在閱讀下面的技術細節之前請訪問下下面的函式庫,然後請給加個星 :

https://github.com/jasync-sql/jasync-sql

轉換本身包括兩個主要步驟:

  • 自動逐行搜尋替換指令碼內容,節省一些無謂的打字時間;

  • 人工稽核程式碼,修改所有編譯錯誤,決定怎樣進行轉換,並改進指令碼。


640?wx_fmt=png

指令碼


指令碼其實是一段非常簡單無腦的kscript程式碼(https://github.com/holgerbrandl/kscript),感覺都沒必要貼出來。一些程式碼行甚至都沒有替換成合法的語句(比如模式匹配和型別強制轉換的部分)。

我沒有時間也沒有能力使用antlr(http://www.antlr.org/)之類的東西去寫個語法分析器或完整的轉換器,而且我還有一些非常特殊的需求。但你要是有興趣的話可以試試。

話不多說,下面是指令碼的簡化版本:

 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)
9catch (e: Exception) {
10  e.printStackTrace()
11}
12
13fun convert(lines: List<String>): List<String> {
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<String>()
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<Byte>("" ByteArray(")
35        .replace(" Array<Char>("" 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<String>) {
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<String> = 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("\n")
94  File(fileName).writeText(fileContent)
95}

這個指令碼是用kscript編寫的(https://github.com/holgerbrandl/kscript),它接受一個引數:可以是副檔名已經改為.kt的Scala檔案,也可以傳遞目錄,如果是目錄則該指令碼會遞迴轉換目錄中的所有檔案。

這個指令碼會進行一些非常簡單的逐行查詢替換:def替換成fun,trait替換成interface,等等。沒什麼特別的東西。因為我前面說過,兩者語法很相似,這一點起了很大作用。如果轉換成Java則可能會更麻煩。


640?wx_fmt=png

經驗教訓,以及我做出的決定


我寫這篇文章的目的就是記錄下我做過的事情。一些檔案仍然需要轉換,同時專案中還有其他人,所以這篇文章會有用的。

下面的專案順序不分先後,以後也可能會更新。

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 → List

  • IndexedSeq → List

  • ArrayBuffer → 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的型別替換了,其中用到了一些擴充套件的幫助函式:

https://github.com/jasync-sql/jasync-sql/blob/master/db-async-common/src/main/java/com/github/jasync/sql/db/util/NullableUtils.kt

這裡我發現Kotlin的方法更好,因為Scala有時使用Option,有時卻直接使用null。

相關推薦

歷時 7 Scala 程式碼移植 Kotlin

【CSDN編者按】去年,Google 宣佈 Kotlin 正式成為 Android 官方開發語言,由此引發了遷移 Kotlin 的一股熱潮。在本文中,作者分享了他在七天內把程式碼從 Scala 移植到 Kotlin 的經過,以及從中吸取的經驗教訓。 以下為譯文:

入門十就用50Python程式碼爬到整個網站

  這篇文章是利用aiohttp這個庫來進行說明的。 如果爬蟲需要展現速度,我覺得就是去下載圖片吧,原本是想選擇去煎蛋那裡下載圖片的,那裡的美女圖片都是高質量的,我稿子都是差不多寫好了的,無奈今天重新看下,妹子圖的入口給關了,至於為什麼關呢,大家可以去看看昨天好奇心日報的關停

那個說程序員就應該加班的HR句MMP想對你說

不相信 現在 包括 獲得 的人 小夥伴 朋友 朋友圈 研發 先說我的觀點:程序員工資高,是因為有能力,而不是會加班! 事情是這樣~~~ 這幾天我公司研發部的小夥伴連續好幾天加班到很晚,我在他們工位後面看著他們疲憊的身影,心中五味雜陳。 於是,我在朋友圈感慨了一句: 其他部門

在醫院五「鏈路追蹤」整明白

![封面](https://img-blog.csdnimg.cn/img_convert/529165137ac22ad2f94cc6c1ae7f4702.png) 封面圖是 凌晨 3點半起來更文的鎖屏桌面。 ## 前言 從上週六 7 號到今天的 11 號,我都在醫院,小孩因肺炎已經住院了,我白天和晚

2019年的第一給自己定份價值50的學習計劃

1. 2018年已經永遠地消逝了,就好像一壺老酒,喝進肚子裡後就再也不可能吐出來了。 今天是2019年的第一天,趕緊花心思制定一份2019年的學習計劃吧!能有多詳細就有多詳細。 有些人覺得,學習計劃有什麼好制定的——今天是一天,明天是一天,後天還是一天,一天一天的就這樣過好了。 但我不這麼覺得。

真正的體會到。。。

王者榮耀 業務需求 老天爺 沒有 兩個 說道 打包 設置問題 線上測試 都說文章有沒有好的內容不中用,重要的是有一個醒目的標題,或者一個響亮又誘人的名字。也不知道我這個題目怎麽樣。。。。 說說這兩天發生的事吧, 0729 新一輪的升級,我們做得時電商項目本次升級跟往常不一樣

用 Rust 重寫已有 19 年曆史的 C++ 庫

從版本 56 開始,Firefox 瀏覽器支援一種新的字元編碼轉換庫,叫做 encoding_rs。它是用 Rust 編寫的,代替了從 1999 年就開始使用的 C++ 編寫的字元編碼庫 uconv。最初,所有呼叫該字元編碼轉換庫的程式碼都是 C++,所以儘管新的庫是用 Rust

就這樣只用就開發出款APP

​眾所周知,開發APP一直是件非常不容易的事,需要Android和iOS兩班開發人員,還得有設計、測試等等分工,根據APP的複雜程度,開發週期兩三個月乃至半年,再加上後期幾乎無止境的維護和更新,沒有七八個人、幾十萬資金,很難做出一個像樣的APP。 而如今,一個人花一天時

當有不在因為你的身邊不再需要

   幾年之前,村裡偶遇當時可能被你迷人的外表所迷惑,加了聯絡方式,之後石沉大海不再聯絡    最近一兩年遊戲讓我們 在一次的溝通但是感覺這東西真讓人琢磨不定。    去年的集五福讓我們再一次的聯絡,但是卻降低了我在你心中的形象。    往後我的一句我想你,劈頭蓋臉的神經病澆

查詢近7近1個月近3個月每天的資料量查詢近年每個月的資料量

統計近7天每天,近一個月每天,近三個月每天,近一年每個月的新增數量,用於畫折線圖,由於是根據create_time欄位統計的,所以如果有一天沒有新增,就會缺少這一天的日期,要對日期進行補充,當天沒有新增的new_count置為0,所以要建立一個日期表calendar 1、查

那些學前端的日子

前端 js框架 javascript同學們,到點了,要鎖門了,趕緊走吧!”後勤大叔又來催我們了,似乎每個深夜我們的離開都是伴隨著這句熟悉的話語。同學們關掉電腦,依依不舍地走出了教室,腦海中還在回 想著剛才敲得那一行行代碼。樓道裏,大家都在議論著,還有三個星期就要畢業了。是啊,幾個月的學習時光匆匆而過,轉眼間

mysql如何出查出最近7最近30最近n的記錄?

所有 說了 group by created value 分享 mage blog ifnull 已查詢瀏覽量為例:原始數據如下: 思路分析:數據有了,統計某一天的瀏覽量,所有瀏覽量,或固定時間段內的瀏覽量在這裏我們就不多說了,大家都會,那我們是如何將最近七天的數據統

按照日期查詢最近7一個月功能

spa color get class cnblogs gte else 日期查詢 pan      if ("30".equals(date)) { Calendar now = Calendar.getInstance();

大家好名程序員這就是2017年的……

bnt stk png jpeg 願望 手機 copy pbr 分享圖片 註:本文系轉載。 十年生死兩茫茫 寫程序,到天亮 千行代,Bug 何處藏 縱使上線又怎樣 朝令改,夕斷腸 領導每天新想法 天天改,日日忙 相顧無言惟有淚千行 每晚燈火闌珊處夜難寐,加班忙

購物車重寫遍購物車

hose pin 技術分享 span 余額 png pri 商品 macbook product_list = [(‘iphone‘,5888),(‘macbook‘,12000),(‘meilv‘,998),(‘pen‘,5),(‘beizi‘,1),(‘text‘,2

字典和列表性能哪家強百萬隨機字符來為你揭曉

tro 哈希表 並發 快速 random模塊 符號 基礎 運行時 散列表 Python中有兩個非常常用的數據結構,列表和字典。在做數據存儲的時候,到底二者哪家強,字典還是列表,還是差不多呢。與其猜測,不如我們用數據說話! 思路: 生成一個很大的文本文件分別用列表和字典來存儲

成為博彩網站“代理”後好兄弟坑到跑路

在被高利貸威脅割腎還債後,阿才跑路了。他到最後都不知道,我這個拉著他投注的好兄弟,其實是博彩網站的“代理”。 投注 2012年,我從部隊退伍後,去了深圳一物業公司做保安。因為性格開朗,我很快和20多個同事打成了一片。 也是在那時候,我認識了阿才。都是湖南人,年紀也差不多,我倆走得很近

同宿舍的程式設計師畢業五年的現狀:有人年薪百萬有人月薪

工作五年是個分水嶺,大部分程式設計師從初級工程師成長為中高階工程師,薪資較剛畢業時翻幾番。 同一個宿舍畢業的人有不同的發展軌道,有人年薪百萬,有人仍在基層搬磚。小編採訪幾位工作五年的程式設計師,談談他們的現狀。   1.李亞軍:專注在一家公司裡走技術管理路線,現不寫程式碼專注撕