在 GitHub 上編輯此頁面

運算子的規則

中綴運算子的規則在某些部分已變更

首先,字母數字方法只能在其定義帶有 infix 修飾詞時用作中綴運算子。

其次,建議(但未強制執行)使用 @targetName 註解 來擴充符號運算子的定義。

最後,語法變更允許在多行表達式中將中綴運算子寫在左側。

infix 修飾詞

方法定義上的 infix 修飾詞允許將方法用作中綴運算。範例

import scala.annotation.targetName

trait MultiSet[T]:

  infix def union(other: MultiSet[T]): MultiSet[T]

  def difference(other: MultiSet[T]): MultiSet[T]

  @targetName("intersection")
  def *(other: MultiSet[T]): MultiSet[T]

end MultiSet

val s1, s2: MultiSet[Int]

s1 union s2         // OK
s1 `union` s2       // also OK but unusual
s1.union(s2)        // also OK

s1.difference(s2)   // OK
s1 `difference` s2  // OK
s1 difference s2    // gives a deprecation warning

s1 * s2             // OK
s1 `*` s2           // also OK, but unusual
s1.*(s2)            // also OK, but unusual

涉及字母數字運算子的中綴運算已棄用,除非符合下列條件之一

  • 運算子定義帶有 infix 修飾詞,或
  • 運算子是用 Scala 2 編譯,或
  • 運算子後接開括號。

字母數字運算子是由字母、數字、$_ 字元,或任何 Unicode 字元 c 組成,其中 java.lang.Character.isIdentifierPart(c) 回傳 true

涉及符號運算子的中綴運算總是允許的,因此對具有符號名稱的方法而言,infix 是多餘的。

infix 修飾詞也可以給予類型

infix type or[X, Y]
val x: String or Int = ...

動機

infix 修飾詞的目的是在方法或類型如何應用的程式碼庫中達到一致性。其想法是方法的作者決定該方法應當作為中綴運算子或在一般應用中應用。然後,使用網站一致地實作該決定。

詳細資料

  1. infix 是軟修飾詞。它被視為一般識別碼,但修飾詞位置除外。

  2. 如果方法覆寫另一個方法,它們的中綴註解必須一致。它們都註解為 infix,或都沒有註解。

  3. infix 修飾詞可以給予方法定義。infix 方法的第一個非接收器參數清單必須精確定義一個參數。範例

    infix def op1(x: S): R             // ok
    infix def op2[T](x: T)(y: S): R    // ok
    infix def op3[T](x: T, y: S): R    // error: two parameters
    
    extension (x: A)
      infix def op4(y: B): R          // ok
      infix def op5(y1: B, y2: B): R  // error: two parameters
    
  4. infix 修飾詞也可以給予具有兩個類型參數的類型、特質或類別定義。中綴類型如下

    infix type op[X, Y]
    

    可以使用中綴語法應用,即 A op B

  5. 為了順利遷移到 Scala 3.0,字母數字運算子將僅從 Scala 3.1 開始不建議使用,或是在 Dotty/Scala 3 中給予 -source future 選項時。

@targetName 註解

建議符號運算子的定義包含 @targetName 註解,提供具有字母數字名稱的運算子編碼。這有幾個好處

  • 它有助於 Scala 與其他語言之間的互通性。可以使用其目標名稱從另一種語言呼叫 Scala 定義的符號運算子,這樣就不必記住符號名稱的低階編碼。
  • 它有助於堆疊追蹤和其他執行時期診斷的可讀性,其中會顯示使用者定義的字母數字名稱,而不是低階編碼。
  • 它可作為文件工具,提供替代的正規名稱作為符號運算子的別名。這也讓定義更容易在搜尋中找到。

語法變更

中綴運算子現在可以在多行表達式的行首出現。範例

val str = "hello"
  ++ " world"
  ++ "!"

def condition =
  x > 0
  ||
  xs.exists(_ > 0)
  || xs.isEmpty

以前,這些表達式會被拒絕,因為編譯器的分號推論會將延續 ++ " world"|| xs.isEmpty 視為獨立的陳述式。

為了讓此語法運作,規則已修改為不推論前導中綴運算子前面的分號。前導中綴運算子

  • 符號識別碼,例如 +approx_==,或反引號中的識別碼,其
  • 開始新的一行,且
  • 不接續空白行,且
  • 接續至少一個空白字元和一個可以開始表達式的代碼。
  • 此外,如果運算子出現在其自己的行中,下一行必須至少與運算子具有相同的縮排寬度。

範例

freezing
  | boiling

這被辨識為單一中綴運算。與

freezing
  !boiling

這被視為兩個陳述式,freezing!boiling。不同之處在於,只有第一個範例中的運算子後面接續空格。

另一個範例

println("hello")
  ???
  ??? match { case 0 => 1 }

這段程式碼被辨識為三個不同的陳述式。??? 在語法上是一個符號識別碼,但它的兩個出現都沒有後接一個空白和一個可以開始表達式的代碼。

一元運算子

一元運算子不能有明確的參數清單,即使它們是空的。一元運算子是一個名為「unary_op」的方法,其中 op+-!~ 之一。