此文件頁面專屬於 Scala 3,可能涵蓋 Scala 2 中沒有的新概念。除非另有說明,此頁面中的所有程式碼範例都假設您使用 Scala 3。
如果您建立一個名為 Hello.scala 的 Scala 3 原始碼檔案
@main def hello = println("Hello, world")
然後使用 scalac
編譯該檔案
$ scalac Hello.scala
您會注意到在其他結果檔案中,scalac
會建立具有 .tasty 副檔名的檔案
$ ls -1
Hello$package$.class
Hello$package.class
Hello$package.tasty
Hello.scala
hello.class
hello.tasty
這會引發一個自然的問題,「什麼是 tasty?」
什麼是 TASTy?
TASTy 是一個縮寫,源自術語Typed Abstract Syntax Trees。它是 Scala 3 的高階交換格式,在本文中我們將稱之為Tasty。
首先要了解的一件重要事情是 Tasty 檔案是由 scalac
編譯器產生的,並且包含有關您的原始碼所有資訊,包括程式的語法結構,以及有關類型、位置,甚至文件編寫的完整資訊。Tasty 檔案包含的資訊比在 JVM 上執行的.class 檔案多得多。(稍後會詳細說明。)
在 Scala 3 中,編譯過程如下所示
+-------------+ +-------------+ +-------------+
$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class |
+-------------+ +-------------+ +-------------+
^ ^ ^
| | |
Your source TASTy file Class file
code for scalac for the JVM
(contains (incomplete
complete information)
information)
您可以使用 -print-tasty
標記在編譯器上執行.tasty 檔案,以人類可讀的形式檢視其內容。您也可以使用 -decompile
標記,以類似於 Scala 原始碼的形式檢視反編譯的內容。
$ scalac -print-tasty hello.tasty
$ scalac -decompile hello.tasty
.class 檔案的問題
由於 類型擦除 等問題,.class 檔案實際上只是您程式碼的不完整表示。展示此問題的一個簡單方法是使用 List
範例。
類型擦除表示,當您撰寫如下 Scala 程式碼,而該程式碼應在 JVM 上執行時
val xs: List[Int] = List(1, 2, 3)
該程式碼會編譯成.class 檔案,而該檔案需要與 JVM 相容。由於此相容性需求,該類別檔案中的程式碼(您可以使用 javap
命令查看)最後會變成如下所示
public scala.collection.immutable.List<java.lang.Object> xs();
此 javap
指令輸出顯示類別檔案中所包含內容的 Java 表示。請注意在此輸出中,xs
不再 定義為 List[Int]
;它基本上表示為 List[java.lang.Object]
。為了讓您的 Scala 程式碼與 JVM 搭配使用,Int
類型已被抹除。
稍後,當您在 Scala 程式碼中存取 List[Int]
的元素時,如下所示
val x = xs(0)
產生的類別檔案會針對此程式碼列進行強制轉型運算,您可以將其視為類似以下內容
int x = (Int) xs.get(0) // Java-ish
val x = xs.get(0).asInstanceOf[Int] // more Scala-like
同樣地,這是為了相容性而進行的,因此您的 Scala 程式碼可以在 JVM 上執行。然而,我們已經擁有整數清單的資訊會在類別檔案中遺失。這會在嘗試根據已編譯的函式庫編譯 Scala 程式時造成問題。為此,我們需要比類別檔案中通常可用的更多資訊。
而且,此討論僅涵蓋類型抹除的主題。JVM 不了解的每個其他 Scala 建構都會有類似的問題,包括聯合、交集、帶有參數的特質,以及更多 Scala 3 功能。
TASTy 來救援
因此,TASTy 格式會在類型檢查後儲存完整的抽象語法樹 (AST),而不是在 .class 檔案中沒有原始類型的資訊,或只有公開 API(例如 Scala 2.13「Pickle」格式)。儲存整個 AST 有許多優點:它能進行個別編譯、針對不同的 JVM 版本重新編譯、對程式進行靜態分析,以及更多。
重點
因此,這是本節的第一個重點:您在 Scala 程式碼中指定的類型並未完全準確地表示在 .class 檔案中。
第二個重點是要了解在 編譯時間 和 執行時間 可用的資訊之間存在差異
- 在 編譯時間,當
scalac
讀取並分析您的程式碼時,它知道xs
是List[Int]
- 當編譯器將您的程式碼寫入類別檔案時,它會寫入
xs
是List[Object]
,並在其他任何地方都加入型別轉換資訊,其中會存取xs
- 然後在 執行時間 — 您的程式碼在 JVM 內執行時 — JVM 不知道您的清單是
List[Int]
使用 Scala 3 和 Tasty,以下是關於編譯時間的另一個重要注意事項
- 當您撰寫使用其他 Scala 3 函式庫的 Scala 3 程式碼時,
scalac
不再需要讀取其 .class 檔案;它可以讀取其 .tasty 檔案,正如前面提到的,它是您的程式碼的 精確 表示。這對於啟用 Scala 2.13 和 Scala 3 之間的獨立編譯和相容性非常重要。
Tasty 的好處
正如您所想像的,擁有程式碼的完整表示有很多 好處
- 編譯器使用它來支援獨立編譯。
- 基於 Scala 語言伺服器協定 的語言伺服器使用它來支援超連結、指令完成、文件,以及用於尋找參考和重新命名等全域操作。
- Tasty 為新一代的 基於反射的巨集奠定了良好的基礎。
- 最佳化器和分析器可以使用它進行深入的程式碼分析和進階程式碼產生。
相關說明,Scala 2.13.6 有 TASTy 讀取器,而 Scala 3 編譯器也能讀取 2.13 的「Pickle」格式。Scala 3 移轉指南中的 類別路徑相容性頁面 說明了這種跨編譯功能的優點。
更多資訊
總之,Tasty 是 Scala 3 的高階交換格式,而 .tasty 檔案包含原始碼的完整表示,可帶來前一節所概述的優點。
如需更多詳細資訊,請參閱下列資源
- 在 這部影片 中,Scala Center 的 Jamie Thompson 詳細說明了 Tasty 的運作方式及其優點
- 函式庫作者的二進位相容性 討論二進位相容性、原始碼相容性,以及 JVM 執行模型
- Scala 3 轉換的前向相容性 示範在同一個專案中使用 Scala 2.13 和 Scala 3 的技巧
這些文章提供了有關 Scala 3 巨集的更多資訊
TASTyViz 是用來以視覺方式檢查 TASTy 檔案的工具。在撰寫本文時,它仍處於開發的早期階段,因此您可能會遇到缺少功能和使用者體驗不佳的情況,但它在除錯時仍可能很有用。您可以在 這裡 查看它。