已區分的聯集
差別聯集支援的值可以是一些具名案例的其中之一,且每個案例的值和類型可能不同。 差別聯集可用於異質資料、可能具有特殊案例 (包括有效和錯誤案例) 的資料、執行個體彼此之間類型不同的資料,以及作為小型物件階層的替代方案。 此外,遞迴差別聯集可用來表示樹狀資料結構。
語法
[ attributes ]
type [accessibility-modifier] type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
[ member-list ]
備註
差別聯集類似於其他語言中的聯集類型,但有所差異。 如同 C++ 中的聯集類型或 Visual Basic 中的變數類型,儲存在值中的資料不會固定,而可以是數個相異選項之一。 不過,不同於這些其他語言中的聯集,每個可能的選項都會指定「案例識別項」。 案例識別項是此類型物件可能值的各種可能類型名稱;這些值是選擇性的。 如果值不存在,案例就相當於列舉案例。 如果值存在,則每個值可以是指定類型的單一值,或是彙總相同或不同類型之多個欄位的元組。 您可以為個別欄位指定名稱,但名稱是選擇性的,即使為相同案例中的其他欄位指定了名稱也一樣。
差別聯集的存取範圍預設為 public
。
例如,請考慮下列 Shape 類型的宣告。
type Shape =
| Rectangle of width : float * length : float
| Circle of radius : float
| Prism of width : float * float * height : float
上述程式碼會宣告差別聯集 Shape,可以有下列三個案例中任何案例的值:Rectangle、Circle 和 Prism。 每個案例都有一組不同的欄位。 Rectangle 案例有兩個具名欄位,類型都是 float
,分別名為 width 和 length。 Circle 案例只有一個具名欄位 radius。 Prism 案例有三個欄位,其中兩個欄位(width 和 height) 是具名欄位。 未命名的欄位稱為匿名欄位。
您可以根據下列範例提供具名和匿名欄位的值來建構物件。
let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)
此程式碼顯示您可以在初始化中使用具名欄位,也可以依賴宣告中的欄位排序,並只提供每個欄位的值。 先前程式碼中 rect
的建構函式呼叫會使用具名欄位,但 circ
的建構函式呼叫會使用排序。 您可以混合已排序的欄位和具名欄位,如 prism
的建構中所示。
option
類型是 F# 核心程式庫中的簡單差別聯集。 option
類型的宣告方式如下。
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
先前的程式碼指定類型 Option
是具有兩個案例 Some
和 None
的差別聯集。 Some
案例具有關聯值,其中包含一個匿名欄位 (以型別參數 'a
表示其類型)。 None
案例沒有關聯值。 因此,option
類型會指定具有特定類型值或沒有值的泛型型別。 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."
在模式比對運算式中,您可以使用具名欄位來指定差別聯集相符項目。 針對先前宣告的 Shape 類型,您可以使用具名欄位 (如下列程式碼所示) 來擷取欄位的值。
let getShapeWidth shape =
match shape with
| Rectangle(width = w) -> w
| Circle(radius = r) -> 2. * r
| Prism(width = w) -> w
一般而言,您可以使用案例識別項,而不需要使用聯集名稱來限定這些識別項。 如果想要一律使用聯集名稱來限定名稱,您可以將 RequireQualifiedAccess 屬性套用至聯集類型定義。
解除包裝差別聯集
在 F# 中,差別聯集通常用於定義域模組化,以包裝單一類型。 您也可以輕鬆地透過模式比對來擷取基礎值。 您不需要針對單一案例使用比對運算式:
let ([UnionCaseIdentifier] [values]) = [UnionValue]
下列範例為其示範:
type ShaderProgram = | ShaderProgram of id:int
let someFunctionUsingShaderProgram shaderProgram =
let (ShaderProgram id) = shaderProgram
// Use the unwrapped value
...
函式參數中也直接允許模式比對,因此您可以在該處解除包裝單一案例:
let someFunctionUsingShaderProgram (ShaderProgram id) =
// Use the unwrapped value
...
結構差別聯集
差別聯集也能以結構表示。 若要這麼做,請使用 [<Struct>]
屬性。
[<Struct>]
type SingleCase = Case of string
[<Struct>]
type Multicase =
| Case1 of Case1 : string
| Case2 of Case2 : int
| Case3 of Case3 : double
由於這些是實值型別而非參考型別,因此與參考差別聯集相較下,有額外的考量:
- 它們會複製為實值型別,並具有實值型別語意。
- 您無法搭配多案例結構差別聯集使用遞迴類型定義。
- 您必須為多案例結構差別聯集提供唯一的案例名稱。
使用差別聯集而非物件階層
您通常可以使用差別聯集作為小型物件階層的更簡單替代方案。 例如,您可以使用下列差別聯集,而不是具有 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
的樹狀結構。
如果樹狀結構中的節點是異質節點,則差別聯集會正常運作。 在下列程式碼中,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 [ "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。
成員
您可以在差別聯集上定義成員。 下列範例示範如何定義屬性並實作介面:
open System
type IPrintable =
abstract Print: unit -> unit
type Shape =
| Circle of float
| EquilateralTriangle of float
| Square of float
| Rectangle of float * float
member this.Area =
match this with
| Circle r -> Math.PI * (r ** 2.0)
| EquilateralTriangle s -> s * s * sqrt 3.0 / 4.0
| Square s -> s * s
| Rectangle(l, w) -> l * w
interface IPrintable with
member this.Print () =
match this with
| Circle r -> printfn $"Circle with radius %f{r}"
| EquilateralTriangle s -> printfn $"Equilateral Triangle of side %f{s}"
| Square s -> printfn $"Square with side %f{s}"
| Rectangle(l, w) -> printfn $"Rectangle with length %f{l} and width %f{w}"
常見屬性
以下是差別聯集中的常見屬性:
[<RequireQualifiedAccess>]
[<NoEquality>]
[<NoComparison>]
[<Struct>]