次の方法で共有


判別共用体

判別共用体は、複数の名前付きケースの 1 つである可能性のある値 (場合によっては、それぞれ異なる値と型を持つ) をサポートします。 判別共用体は異種データに役立ちます。有効なケースやエラーケースを含む、特殊なケースを持つ可能性があるデータ。インスタンス間で種類が異なるデータ。小さなオブジェクト階層の代替として使用できます。 さらに、再帰的判別共用体は、ツリー・データ構造を表すために使用されます。

構文

[ attributes ]
type [accessibility-modifier] type-name =
    | case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
    | case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]

    [ member-list ]

Remarks

判別共用体は他の言語の共用体型と似ていますが、異なる点もあります。 C++ の共用体型や Visual Basic のバリアント型と同様に、値に格納されているデータは固定されません。いくつかの異なるオプションの 1 つを指定できます。 ただし、これらの他の言語の共用体とは異なり、使用可能な各オプションには、ケース識別子が与えられます。 この型のオブジェクトが持ち得るさまざまな値の種類を示すための名前がケース識別子です。これらの値はオプションです。 値が存在しない場合、ケースは列挙ケースと同等です。 値が存在する場合、各値には、指定した型の単一の値、または同じまたは異なる型の複数のフィールドを集計するタプルを指定できます。 個々のフィールドに名前を付けることができますが、同じケースの他のフィールドに名前が付けられている場合でも、名前は省略可能です。

判別共用体のアクセシビリティは、既定で 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 の 3 つのケースの値を持つことができます。 各ケースには、異なるフィールド のセットがあります。 Rectangle ケースには、名前の幅と長さを持つ 2 つの名前付きフィールド (float型の両方) があります。 Circle ケースには、名前付きフィールド (半径) が 1 つだけ含まれています。 Prism ケースには 3 つのフィールドがあり、そのうちの 2 つ (幅と高さ) は名前付きフィールドです。 名前のないフィールドは匿名フィールドと呼ばれます。

次の例に従って、名前付きフィールドと匿名フィールドの値を指定して、オブジェクトを作成します。

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の 2 つのケースを持つ判別共用体であることを指定しています。 Some ケースには、型パラメーター 'aで表される型を持つ 1 つの匿名フィールドで構成される値が関連付けられています。 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# の判別共用体は単一の型を包むためによく使用されます。 パターン マッチングを使用して基になる値を簡単に抽出できます。 1 つのケースにマッチ式を使用する必要はありません。

let ([UnionCaseIdentifier] [values]) = [UnionValue]

次の例は、これを示しています。

type ShaderProgram = | ShaderProgram of id:int

let someFunctionUsingShaderProgram shaderProgram =
    let (ShaderProgram id) = shaderProgram
    // Use the unwrapped value
    ...

パターン マッチングは関数パラメーターでも直接許可されるため、そこで 1 つのケースをラップ解除できます。

let someFunctionUsingShaderProgram (ShaderProgram id) =
    // Use the unwrapped value
    ...

構造体の判別共用体

判別共用体を構造体として表すこともできます。 これは、[<Struct>] 属性を使用して行われます。

[<Struct>]
type SingleCase = Case of string

[<Struct>]
type Multicase =
    | Case1 of string
    | Case2 of int
    | Case3 of double

これらは値型であり、参照型ではないため、参照判別共用体と比較して、追加の考慮事項があります。

  1. これらは値型としてコピーされ、値型セマンティクスを持っています。
  2. マルチケース構造体の判別共用体では、再帰的な型定義は使用できません。

F# 9 より前では、ケースごとに共用体内で一意のケース名を指定する必要がありました。 F# 9 以降では、制限が解除されます。

オブジェクト階層の代わりに判別共用体を使用する

多くの場合、判別共用体は、小さなオブジェクト階層のより単純な代替として使用できます。 たとえば、円、正方形などの派生型を持つ 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

ツリー データ構造への判別共用体の使用

判別共用体は再帰的である可能性があります。つまり、共用体自体を 1 つ以上のケースの型に含めることができます。 再帰判別共用体を使用してツリー構造を作成できます。ツリー構造は、プログラミング言語で式をモデル化するために使用されます。 次のコードでは、再帰的な判別共用体を使用して、バイナリ ツリー データ構造を作成します。 和集合は、整数値と左右のサブツリーを持つノードである Nodeと、ツリーを終了する Tipの 2 つのケースで構成されます。

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) を表します。 その他のケースは再帰的であり、演算 (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}"

ケースの .Is* プロパティ

F# 9 以降、判別共用体では、ケースごとに自動生成された .Is* プロパティが公開されるため、値が特定のケースであるかどうかを確認できます。

これを使用する方法は次のとおりです。

type Contact =
    | Email of address: string
    | Phone of countryCode: int * number: string

type Person = { name: string; contact: Contact }

let canSendEmailTo person =
    person.contact.IsEmail      // .IsEmail is auto-generated

一般的な属性

判別共用体では、次の属性が一般的に使用されます。

  • [<RequireQualifiedAccess>]
  • [<NoEquality>]
  • [<NoComparison>]
  • [<Struct>]

関連項目