已區分的聯集 (F#)

更新:2010 年 9 月

「已區分的聯集」(Discriminated Union) 支援各種具名案例之一的值,而每個案例可能都有不同的值和型別。 已區分的聯集適用於異質資料、可具有特殊案例的資料 (包括有效和錯誤案例)、不同執行個體之型別不同的資料,以及做為小型物件階層的替代項目。 此外,遞迴的已區分聯集是用來代表樹狀資料結構。

type type-name =
   | case-identifier1 [of type1 [ * type2 ...]
   | case-identifier2 [of type3 [ * type4 ...]
   ...

備註

已區分的聯集與其他語言的聯集型別類似,但是有差異。 與 C++ 中的聯集型別或 Visual Basic 中的變數型別相同,儲存在值中的資料並未固定;它可以是多個相異選項的其中一個。 不過,與其他語言的聯集不同的是,每個可能選項都會指定「案例識別項」(Case Identifier)。 案例識別項是此型別之物件可能的各種實值型別的名稱,這些值為選擇性。 如果值不存在,則案例就相當於列舉案例。 如果值存在,則每個值都可以是指定之型別的單一值,或是彙總多個相同或不同型別之值的 Tuple。

option 型別是 F# 核心程式庫中的簡單已區分聯集。 option 型別則宣告如下。

// The option type is a discriminated union.
type Option<'a> =
    | Some of 'a
    | None

前一個程式碼指定 Option 型別是具有 Some 和 None 兩個案例的已區分聯集。 Some 案例具有型別以 'a 型別參數所代表的相關聯值。 None 案例沒有相關聯值。 因此,option 型別指定值為 some 型別或沒有值的泛型型別。 Option 型別也具有較常使用的小寫型別別名 option。

案例識別項可以做為已區分之聯集型別的建構函式。 例如,下列程式碼是用來建立 option 型別的值。

let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None

案例識別項也用於模式比對運算式。 在模式比對運算式中,會針對與個別案例相關聯的值提供識別項。 例如,在下列程式碼中,x 是識別項,已指定與 option 型別之 Some 案例相關聯的值。

let printValue opt =
    match opt with
    | Some x -> printfn "%A" x
    | None -> printfn "No value."

一般而言,可以使用案例識別項,而不需要使用聯集名稱來限定它們。 如果您想要一律使用聯集名稱來限定名稱,則可以將 RequireQualifiedAccess 屬性套用至聯集型別定義。

使用已區分的聯集,而非物件階層

您可以經常使用已區分的聯集做為小型物件階層的簡單替代項目。 例如,可以使用下列已區分的聯集,而非具有 circle、square 等項目之衍生型別的 Shape 基底類別。

type Shape =
  // The value here is the radius.
| Circle of float
  // The value here is the side length.
| EquilateralTriangle of double
  // The value here is the side length.
| Square of double
  // The values here are the height and width.
| Rectangle of double * double

與您用於物件導向實作相同,您可以使用模式比對分支至適當的公式來計算這些數量,而非計算區域或周邊的虛擬方法。 在下列範例中,會根據形狀使用不同的公式來計算區域。

let pi = 3.141592654

let area myShape =
    match myShape with
    | Circle radius -> pi * radius * radius
    | EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
    | Square s -> s * s
    | Rectangle (h, w) -> h * w

let radius = 15.0
let myCircle = Circle(radius)
printfn "Area of circle that has radius %f: %f" radius (area myCircle)

let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square that has side %f: %f" squareSide (area mySquare)

let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle that has height %f and width %f is %f" height width (area myRectangle)

其輸出如下:

Area of circle that has radius 15.000000: 706.858347
Area of square that has side 10.000000: 100.000000
Area of rectangle that has height 5.000000 and width 10.000000 is 50.000000

將已區分的聯集用於樹狀資料結構

已區分的聯集可以是遞迴的,這表示聯集本身可以併入一個或多個案例的型別中。 遞迴的已區分聯集可以用來建立樹狀結構,而樹狀結構是用來透過程式設計語言來模型化運算式。 在下列程式碼中,遞迴的已區分聯集是用來建立二進位樹狀資料結構。 此聯集是由下列兩個案例所組成:Node (具有整數值和左右子樹狀結構的節點) 和 Tip (可結束樹狀結構)。

type Tree =
    | Tip
    | Node of int * Tree * Tree

let rec sumTree tree =
    match tree with
    | Tip -> 0
    | Node(value, left, right) ->
        value + sumTree(left) + sumTree(right)
let myTree = Node(0, Node(1, Node(2, Tip, Tip), Node(3, Tip, Tip)), Node(4, Tip, Tip))
let resultSumTree = sumTree myTree

在前一個程式碼中,resultSumTree 的值為 10。 下圖顯示 myTree 的樹狀結構。

myTree 的樹狀結構

差別等位的樹狀架構圖表

如果樹狀結構中的節點是異質的,則已區分的聯集運作良好。 在下列程式碼中,Expression 型別代表簡單程式設計語言中運算式的抽象語法樹狀結構,支援數字和變數的加法和乘法。 部分聯集案例不是遞迴的,而且代表數字 (Number) 或變數 (Variable)。 其他案例則是遞迴的,而且代表運算 (Add 和 Multiply),其中運算元也是運算式。 Evaluate 函式使用比對運算式來遞迴處理語法樹狀結構。

type Expression = 
    | Number of int
    | Add of Expression * Expression
    | Multiply of Expression * Expression
    | Variable of string

let rec Evaluate (env:Map<string,int>) exp = 
    match exp with
    | Number n -> n
    | Add (x, y) -> Evaluate env x + Evaluate env y
    | Multiply (x, y) -> Evaluate env x * Evaluate env y
    | Variable id    -> env.[id]

let environment = Map.ofList [ "a", 1 ;
                               "b", 2 ;
                               "c", 3 ]

// Create an expression tree that represents
// the expression: a + 2 * b.
let expressionTree1 = Add(Variable "a", Multiply(Number 2, Variable "b"))

// Evaluate the expression a + 2 * b, given the
// table of values for the variables.
let result = Evaluate environment expressionTree1

執行這個程式碼時,result 的值為 5。

請參閱

其他資源

F# 語言參考

變更記錄

日期

記錄

原因

2010 年 9 月

修正程式碼範例。

內容 Bug 修正。