已區分的聯集

差別聯集支援的值可以是一些具名案例的其中之一,且每個案例的值和類型可能不同。 差別聯集可用於異質資料、可能具有特殊案例 (包括有效和錯誤案例) 的資料、執行個體彼此之間類型不同的資料,以及作為小型物件階層的替代方案。 此外,遞迴差別聯集可用來表示樹狀資料結構。

語法

[ 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 是具有兩個案例 SomeNone 的差別聯集。 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

由於這些是實值型別而非參考型別,因此與參考差別聯集相較下,有額外的考量:

  1. 它們會複製為實值型別,並具有實值型別語意。
  2. 您無法搭配多案例結構差別聯集使用遞迴類型定義。
  3. 您必須為多案例結構差別聯集提供唯一的案例名稱。

使用差別聯集而非物件階層

您通常可以使用差別聯集作為小型物件階層的更簡單替代方案。 例如,您可以使用下列差別聯集,而不是具有 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 的樹狀結構。

Diagram that shows the tree structure for myTree.

如果樹狀結構中的節點是異質節點,則差別聯集會正常運作。 在下列程式碼中,Expression 類型以簡單的程式設計語言表示運算式的抽象語法樹狀結構,可支援數字和變數的加法和乘法。 其中一些聯集案例不是遞迴的,並代表數字 (Number) 或變數 (Variable)。 其他案例則是遞迴的,並代表作業 (AddMultiply),其中運算元也是運算式。 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>]

另請參閱