Scala 3 — 書籍

Scala 3 中的主要方法

語言
撰寫單行程式 僅限 Scala 3

Scala 3 提供定義可從命令列呼叫程式的全新方式:將 @main 註解新增至方法中,即可將其轉換為可執行程式的進入點

@main def hello() = println("Hello, World")

若要執行此程式,請將程式碼行儲存在名為 Hello.scala 的檔案中,檔案名稱不必與方法名稱相符,然後使用 scala 執行。

$ scala Hello.scala
Hello, World

帶有 @main 註解的方法可以寫在頂層(如所示),或寫在靜態可存取的物件內。在任一種情況下,程式名稱都是方法名稱,不包含任何物件前置詞。

透過閱讀以下各節或觀看此影片,進一步了解 @main 註解

命令列引數

使用此方法,您的 @main 方法可以處理命令列引數,而這些引數可以有不同的類型。例如,假設此 @main 方法會接收一個 Int、一個 String 和一個可變參數 String* 參數

@main def happyBirthday(age: Int, name: String, others: String*) =
  val suffix = (age % 100) match
    case 11 | 12 | 13 => "th"
    case _ => (age % 10) match
      case 1 => "st"
      case 2 => "nd"
      case 3 => "rd"
      case _ => "th"

  val sb = StringBuilder(s"Happy $age$suffix birthday, $name")
  for other <- others do sb.append(" and ").append(other)
  println(sb.toString)

當您編譯該程式碼時,它會建立一個名為 happyBirthday 的主程式,呼叫方式如下

$ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!

如所示,@main 方法可以有任意數量的參數。對於每個參數類型,都必須有 scala.util.CommandLineParser.FromString 類型類別的 給定實例,它將參數 String 轉換為所需的參數類型。此外,如所示,主方法的參數清單可以以重複參數結束,例如 String*,它會擷取命令列上提供的其他所有參數。

@main 方法實作的程式會檢查命令列上有足夠的參數來填入所有參數,以及參數字串是否可以轉換為所需的類型。如果檢查失敗,程式會終止並顯示錯誤訊息。

$ scala happyBirthday 22
Illegal command line after first argument: more arguments expected

$ scala happyBirthday sixty Fred
Illegal command line: java.lang.NumberFormatException: For input string: "sixty"

使用者定義的類型作為參數

如上所述,編譯器會尋找參數類型 scala.util.CommandLineParser.FromString 類型類別的給定實例。例如,假設您有一個自訂 Color 類型,您想要將其用作參數。您可以像下面這樣執行此操作

enum Color:
  case Red, Green, Blue

given CommandLineParser.FromString[Color] with
  def fromString(value: String): Color = Color.valueOf(value)

@main def run(color: Color): Unit =
  println(s"The color is ${color.toString}")

這適用於程式中的自訂使用者類型,以及您可能從其他函式庫使用的類型。

詳細資訊

Scala 編譯器會從 @main 方法 f 產生一個程式,如下所示

  • 它會在找到 @main 方法的套件中建立一個名為 f 的類別。
  • 該類別有一個靜態方法 main,其簽章與 Java main 方法的簽章相同:它將 Array[String] 作為參數,並傳回 Unit
  • 產生的 main 方法會呼叫方法 f,並使用 scala.util.CommandLineParser.FromString 物件中的方法轉換參數。

例如,上面的 happyBirthday 方法會產生等於下列類別的其他程式碼

final class happyBirthday {
  import scala.util.{CommandLineParser as CLP}
  <static> def main(args: Array[String]): Unit =
    try
      happyBirthday(
          CLP.parseArgument[Int](args, 0),
          CLP.parseArgument[String](args, 1),
          CLP.parseRemainingArguments[String](args, 2)*)
    catch {
      case error: CLP.ParseError => CLP.showError(error)
    }
}

注意:在此產生的程式碼中,<static> 修飾詞表示 main 方法會產生為 happyBirthday 類別的靜態方法。此功能不適用於 Scala 中的使用者程式。一般「靜態」成員會使用物件而非 Scala 中產生。

與 Scala 2 的向後相容性

@main 方法是建議用來產生程式的方法,這些程式可以在 Scala 3 中從命令列呼叫。它們取代了 Scala 2 中先前的做法,也就是建立一個延伸 App 類別的 object

先前 App 的功能,它依賴於「神奇」DelayedInit 特質,不再可用。App 目前仍以有限的形式存在,但它不支援命令列引數,而且未來將會被棄用。

如果程式需要在 Scala 2 和 Scala 3 之間交叉建置,建議使用具有明確 main 方法的 object,以及單一 Array[String] 引數

object happyBirthday {
  private def happyBirthday(age: Int, name: String, others: String*) = {
    ... // same as before
  }
  def main(args: Array[String]): Unit =
    happyBirthday(args(0).toInt, args(1), args.drop(2).toIndexedSeq:_*)
}

請注意,我們在此使用 :_* 傳遞變數引數,它保留在 Scala 3 中以維持向後相容性。

如果您將該程式碼置於名為 happyBirthday.scala 的檔案中,您就可以使用 scalac 編譯它,然後使用 scala 執行它,如先前所示

$ scalac happyBirthday.scala

$ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!

此頁面的貢獻者