判別共用体Discriminated Unions

判別共用体は、数多くの名前付きケースのうちのいずれかである可能性がある値をサポートします。ケースの値や型が、それぞれ異なる場合もあります。Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types. 判別共用体は、異種データ、有効ケースやエラー ケースなどの特殊なケースを持つ可能性のあるデータ、インスタンスごとに型が異なるデータ、小さいオブジェクト階層に対する代替手段などの場合に役立ちます。Discriminated unions are useful for heterogeneous data; data that can have special cases, including valid and error cases; data that varies in type from one instance to another; and as an alternative for small object hierarchies. さらに、再帰的な判別共用体は、ツリー データ構造を表すために使用されます。In addition, recursive discriminated unions are used to represent tree data structures.

構文Syntax

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

    [ member-list ]

コメントRemarks

判別共用体は他の言語の共用体型と似ていますが、異なる点もあります。Discriminated unions are similar to union types in other languages, but there are differences. C++ の共用体型や Visual Basic のバリアント型と同じように、値に格納されるデータは固定ではありません。複数の異なるオプションのいずれかを格納できます。As with a union type in C++ or a variant type in Visual Basic, the data stored in the value is not fixed; it can be one of several distinct options. ただし、これらの他の言語の共用体とは異なり、使用可能な各オプションにはケース識別子が指定されています。Unlike unions in these other languages, however, each of the possible options is given a case identifier. ケース識別子には、使用できる各種の値の型の名前を指定します。指定した型のオブジェクトは値の型として使用できます。値は省略可能です。The case identifiers are names for the various possible types of values that objects of this type could be; the values are optional. 値を省略する場合は、ケースが列挙型のケースと等しくなります。If values are not present, the case is equivalent to an enumeration case. 値がある場合は、各値に、指定した型の単一値、あるいは同じ型または異なる型の複数のフィールドを集約したタプルを使用できます。If values are present, each value can either be a single value of a specified type, or a tuple that aggregates multiple fields of the same or different types. 個々のフィールドには名前を付けることができますが、同じケース内の他のフィールドに名前が付けられていても、名前は省略可能です。You can give an individual field a name, but the name is optional, even if other fields in the same case are named.

判別共用体のアクセシビリティの既定値は publicです。Accessibility for discriminated unions defaults to public.

たとえば、Shape 型の次のような宣言を検討します。For example, consider the following declaration of a Shape type.

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * float * height : float

前のコードでは、判別共用体の Shape を宣言します。これには四角形、円、およびプリズムの 3 つのケースのいずれかの値を指定できます。The preceding code declares a discriminated union Shape, which can have values of any of three cases: Rectangle, Circle, and Prism. 各ケースに異なるフィールド セットがあります。Each case has a different set of fields. 四角形の場合は、2 つの名前付きフィールドがあり、両方とも float 型で、幅と長さの名前が付いています。The Rectangle case has two named fields, both of type float, that have the names width and length. 円の場合は、1 つの名前付きフィールドがあり、半径の名前が付いています。The Circle case has just one named field, radius. Prism ケースには3つのフィールドがあり、そのうち2つ (幅と高さ) は名前付きフィールドです。The Prism case has three fields, two of which (width and height) are named fields. 名前のないフィールドは匿名フィールドと呼ばれます。Unnamed fields are referred to as anonymous fields.

次の例では、名前付きフィールドと匿名フィールドに値を設定してオブジェクトを構築します。You construct objects by providing values for the named and anonymous fields according to the following examples.

let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)

このコードは、初期化時に名前付きフィールドを使用することも、宣言時のフィールドの順序に依存して各フィールドの値のみを順番に提供することもできることを示します。This code shows that you can either use the named fields in the initialization, or you can rely on the ordering of the fields in the declaration and just provide the values for each field in turn. 前のコードの rect のコンストラクターの呼び出しでは名前付きフィールドを使用しますが、circ のコンストラクター呼び出しでは順序を使用します。The constructor call for rect in the previous code uses the named fields, but the constructor call for circ uses the ordering. prism の構造のように、順序付きフィールドと名前付きフィールドを混在させることができます。You can mix the ordered fields and named fields, as in the construction of prism.

option 型は、F# コア ライブラリの単純な判別共用体です。The option type is a simple discriminated union in the F# core library. option 型は、次のように宣言されます。The option type is declared as follows.

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

このコードでは、Option 型が、SomeNone の 2 つのケースを持つ判別共用体として指定されています。The previous code specifies that the type Option is a discriminated union that has two cases, Some and None. Some ケースには、型が型パラメーター 'a によって表される 1 つの匿名フィールドで構成される、関連付けられた値があります。The Some case has an associated value that consists of one anonymous field whose type is represented by the type parameter 'a. None ケースには、関連する値はありません。The None case has no associated value. したがって、option 型は、なんらかの型の値を持つジェネリック型、または値を持たないジェネリック型を指定します。Thus the option type specifies a generic type that either has a value of some type or no value. また、Option 型には小文字の型のエイリアス option があり、より一般的に使用されます。The type Option also has a lowercase type alias, option, that is more commonly used.

ケース識別子は、判別共用体型のコンストラクターとして使用できます。The case identifiers can be used as constructors for the discriminated union type. たとえば、option 型の値を作成するには、次のようなコードが使用されます。For example, the following code is used to create values of the option type.

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

ケース識別子は、パターン マッチ式でも使用されます。The case identifiers are also used in pattern matching expressions. パターン マッチ式では、個別のケースに関連付けられる値に対して識別子が提供されます。In a pattern matching expression, identifiers are provided for the values associated with the individual cases. たとえば、次のコードの x は、Some 型の option ケースと関連付けられた値が指定された識別子です。For example, in the following code, x is the identifier given the value that is associated with the Some case of the option type.

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

パターン一致式では、名前付きフィールドを使用して判別共用体一致を指定できます。In pattern matching expressions, you can use named fields to specify discriminated union matches. 前に宣言された Shape 型には、次のコードに示すように、フィールドの値を抽出するために名前付きフィールドを使用できます。For the Shape type that was declared previously, you can use the named fields as the following code shows to extract the values of the fields.

let getShapeWidth shape =
    match shape with
    | Rectangle(width = w) -> w
    | Circle(radius = r) -> 2. * r
    | Prism(width = w) -> w

通常は、共用体の名前で、修飾せずにケース識別子を使用できます。Normally, the case identifiers can be used without qualifying them with the name of the union. 名前を共用体の名前で常に修飾する場合は、 RequireQualifiedAccess属性を共用体型の定義に適用します。If you want the name to always be qualified with the name of the union, you can apply the RequireQualifiedAccess attribute to the union type definition.

判別共用体のラップ解除Unwrapping Discriminated Unions

判別F#共用体では、単一の型をラップするためにドメインモデリングでよく使用されます。In F# Discriminated Unions are often used in domain-modeling for wrapping a single type. パターンマッチングを使用して基になる値を簡単に抽出することもできます。It's easy to extract the underlying value via pattern matching as well. 1つのケースに一致式を使用する必要はありません。You don't need to use a match expression for a single case:

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

この動作を次の例で示します。The following example demonstrates this:

type ShaderProgram = | ShaderProgram of id:int

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

関数のパラメーターでは、パターンマッチングも直接許可されているので、1つのケースをラップ解除できます。Pattern matching is also allowed directly in function parameters, so you can unwrap a single case there:

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

構造体の判別共用体Struct Discriminated Unions

判別共用体を構造体として表すこともできます。You can also represent Discriminated Unions as structs. これは、[<Struct>] 属性を使用して行われます。This is done with the [<Struct>] attribute.

[<Struct>]
type SingleCase = Case of string

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

これらは値型であり、参照型ではないため、参照判別共用体の場合と比較して、追加の考慮事項があります。Because these are value types and not reference types, there are extra considerations compared with reference discriminated unions:

  1. 値型としてコピーされ、値型のセマンティクスを持ちます。They are copied as value types and have value type semantics.
  2. Multicase struct 判別共用体と共に再帰型定義を使用することはできません。You cannot use a recursive type definition with a multicase struct Discriminated Union.
  3. Multicase struct 判別共用体には、一意のケース名を指定する必要があります。You must provide unique case names for a multicase struct Discriminated Union.

オブジェクト階層ではなく、判別共用体を使用する場合Using Discriminated Unions Instead of Object Hierarchies

多くの場合、小さいオブジェクト階層をさらに簡単に表すために判別共用体を使用できます。You can often use a discriminated union as a simpler alternative to a small object hierarchy. たとえば、円や正方形などの派生型を持つ Shape 基底クラスの代わりに、次の判別共用体を使用できます。For example, the following discriminated union could be used instead of a Shape base class that has derived types for circle, square, and so on.

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

オブジェクト指向の実装で使用される、面積や周を計算するための仮想メソッドの代わりに、パターン マッチを使用して、これらの量を計算するための適切な式に分岐できます。Instead of a virtual method to compute an area or perimeter, as you would use in an object-oriented implementation, you can use pattern matching to branch to appropriate formulas to compute these quantities. 次の例では、図形に応じて、異なる数式を使用して面積を計算しています。In the following example, different formulas are used to compute the area, depending on the shape.

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)

出力は次のようになります。The output is as follows:

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

ツリー データ構造への判別共用体の使用Using Discriminated Unions for Tree Data Structures

判別共用体は再帰的に使用できます。つまり、共用体自体を 1 つ以上のケースの型に含めることができます。Discriminated unions can be recursive, meaning that the union itself can be included in the type of one or more cases. 再帰的な判別共用体を使用してツリー構造を作成でき、このツリー構造をプログラミング言語での式のモデル化に使用できます。Recursive discriminated unions can be used to create tree structures, which are used to model expressions in programming languages. 次のコードでは、再帰的な判別共用体を使用して、バイナリ ツリーのデータ構造を作成しています。In the following code, a recursive discriminated union is used to create a binary tree data structure. この共用体を構成する 2 つのケースは、整数値および左と右のサブツリーを持つノードである Node と、ツリーを終了する Tip です。The union consists of two cases, Node, which is a node with an integer value and left and right subtrees, and Tip, which terminates the tree.

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 です。In the previous code, resultSumTree has the value 10. 次の図は、myTree のツリー構造を示しています。The following illustration shows the tree structure for myTree.

MyTree のツリー構造を示す図。

判別共用体は、ツリーのノードが異種の場合でも、問題なく機能します。Discriminated unions work well if the nodes in the tree are heterogeneous. 次のコードの Expression 型は、数と変数の加算と乗算をサポートする簡単なプログラミング言語での式の抽象構文ツリーを表します。In the following code, the type Expression represents the abstract syntax tree of an expression in a simple programming language that supports addition and multiplication of numbers and variables. 共用体の一部のケースは再帰的ではなく、数 (Number) または変数 (Variable) を表します。Some of the union cases are not recursive and represent either numbers (Number) or variables (Variable). 他のケースは再帰的で、演算 (Add および Multiply) を表し、オペランドも式です。Other cases are recursive, and represent operations (Add and Multiply), where the operands are also expressions. Evaluate 関数では、match 式を使用して再帰的に構文ツリーを処理しています。The Evaluate function uses a match expression to recursively process the syntax tree.

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 になります。When this code is executed, the value of result is 5.

メンバーMembers

判別共用体のメンバーを定義することができます。It is possible to define members on discriminated unions. 次の例は、プロパティを定義し、インターフェイスを実装する方法を示しています。The following example shows how to define a property and implement an interface:

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 -> 2.0 * Math.PI * r
        | 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 and width %f" l w

共通属性Common attributes

判別共用体では、一般的に次の属性が見られます。The following attributes are commonly seen in discriminated unions:

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

参照See also