已區分的聯集 (F#)
「已區分的聯集」(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。