判別共用体の概要
判別共用体は、複数の "ケース(= case)" を持ち、いずれかのケースになりえるような値を表現します。また、判別共用体は「異種データ」や「有効/エラーなどの特殊ケースを持つ可能性のあるデータ」、「インスタンスごとに型が異なるデータ」、「小さいオブジェクト階層に対する代替手段」を実現するために役立ちます。さらに、再帰的な判別共用体はツリー構造を表すために利用されます。
今ひとつわかりにくいですね。もう少し砕けた説明をするならば、判別共用体は列挙体と共用体を組み合わせたような型です。今のところは「便利機能がついている列挙体」という認識でも構いません。この項を通じて少しでも判別共用体の利便性をお伝えできればと思います。
また、判別共用体はF#の型の中でもとても便利な型の一つですので利用シーンも多いと思います。使えるようになると非常に強力な武器となりますので、ぜひ使えるようになってください。
// ------------------
// [ 構文 ]
// ------------------
[<attribute>]
type [ accessibility-modifier ] type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
[ member-list ]
判別共用体は他の言語の 共用体型(= union) と似ていますが異なる点も多く存在します。判別共用体はC/C++の共用体と同じように、値に格納されるデータ幅は固定されていません。また、その値は定義されているケースのいずれかに格納されます。ただし、他の言語の共用体とは異なり判別共用体の各ケースには ケース識別子(= case-identifier) という情報が付与されています。
ケース識別子は、宣言した判別共用体型の取りうる値のさまざまな型に名前をつけたものですが、型情報(= 型フィールド or type-field)を省略することも可能です。型フィールドを省略し、ケース識別子のみを宣言した場合は列挙型と同等の機能を提供します。また、型は単一の型でも複合型(= tuple)でも、どちらでも構いません。さらに、型フィールドには名前を付けることも可能です(= filed-name)。
判別共用体のアクセシビリティはデフォルトで public となりますので覚えておきましょう。
サンプルでみる判別共用体
前節で判別共用体の構文を紹介しましたが、おそらくF#を学習して間もない方には非常に難解なものに見えてしまうかもしれません。そのため本節ではサンプルを利用して、実際の値と構文の説明がどう対応しているかを見ていきたいと思います。
今回は様々な形状を表す Shape型 を使って、判別共用体を見ていきましょう。
Shape型は [ attributes ] と [ member-list ] 以外の要素を内包している型です。[ accessibility-modifier ] についてはデフォルトで public となっているため省略可能ですが項目を紹介しやすくするために明示的に表記しています。
下記の表は内包している要素と構文の要素名とを対応付けたものとなります。ただし、重複する内容のものに関しては省略しています。
要素名 | 対応するShape型の要素 |
---|---|
accessibility-modifier | public |
type-name | Shape |
case-identifier1 | Rectangle |
fieldname1 | width |
type1 | float |
fieldname2 | height |
type2 | float |
構文だけみるとなんだか難しそうですが、実際に定義することは非常に簡単です。[ type-name ] という大きなまとまりを宣言し、次いでそれに所属する [ case-identifier ] を宣言すればよいわけです。もし [ case-identifier ] に何かしらの型情報を付与したい場合は [ type ] を宣言してあげればよいだけです。また、その [ type ] にわかりやすいように名前を付けたい場合は [ fieldname ] をつけてあげることによって、名前をつけてあげることができます。[ fieldname ] をつけることによって、値の初期化のときや、あとで見たときにその [ type ] が何を表しているかわかりやすくなります。
判別共用体のShape型全体に目を向けてみましょう。
Shape型は、Rectangle / Circle / Prism の3つのケースのいずれかの値を取り得るような判別共用体型です。各ケースには異なる一連のフィールドが存在しています。Rectangleケース には2つの名前付きフィールド(= width, height)があり、両方ともfloat型です。Circleケースには1つの名前付きフィールド(= radius)があり、それはfloat型です。Prismケースには3つのフィールドがあり、そのうち2つのフィールド(= width, height)は名前付きですが、1つは名前のないフィールドです。名前のないフィールドのことを匿名フィールドといいます。
Shape型を使うことも非常に簡単です。サンプルの9~11行目のように、ケース名に対してそれらのフィールド要素を順番に指定すればよいだけです。フィールド名を指定する場合は順番はどうでもよいですが、フィールド名を指定しない場合は、フィールドを宣言した順に値を指定しなければなりません。
このように判別共用体を宣言することや、値を作成することは容易なことがわかると思います。次節では実際に判別共用体を使うサンプルを紹介していきたいと思います。
利用例
判別共用体はさまざまなシーンで利用されますが、この option型 が組み込み型の中でもよく利用されます。option型の使い方についてはこちらのページで紹介しているので、ここでは詳細な使い方の説明は避けさせていただきます。
今回の利用例は前節で利用した Shape型 をサンプルに話をすすめますが、Shape型 だけに焦点をあてた内容ではありません。この節では「判別共用体とパターンマッチ」や「ラップ解除」などの話をしますが、これは Shape型 特有のものではありません。判別共用体全体に関係する話ですので、判別共用体の使い方を忘れたらこのページに戻ってきてみてください。
繰り返しの紹介とはなりますが、以下が今回のサンプルである Shape型 となります。
判別共用体とパターンマッチ
判別共用体の概要 で少し話題に出したように、判別共用体の各ケースには ケース識別子 というある種のIDが振られています。そのため、判別共用体は名前付きフィールドを利用してパターンマッチさせることができます。次の例ではパターンマッチ式を利用して、各ケースに応じた処理を実行させています。サンプルからわかる通り、引数に判別共用体(今回はShape型)を渡し、その渡された判別共用体の各ケースでパターンマッチをしています。また、各ケースのところで対象のケースの対応するフィールドをタプルで受け取るようにすることで、続く処理部で利用することができます。Rectangleの場合、(width:folat * height:float) で構成されているので、 "Rectangle (w, h) -> 処理部" とすれば処理部で width と height を使えるわけですね。
それでは、一部のフィールドだけを取得することはできないのでしょうか?F#の判別共用体とパターンマッチにはそういった要望に応えるための機能が備わっています。
以下は簡単な利用例となります。
使い方も簡単ですね。パターンマッチで特定のフィールド名を指定するだけで値を取得することができます。
値型の判別共用体
判別共用体はデフォルトで参照型ですが [<Struct>] 属性を指定することで値型にすることが可能です。値型の判別共用体は通常の判別共用体とほぼ同様の使い方ができますが、(1)値型のセマンティクスを持つ点と、(2)各ケースのフィールドには必ず fieldname を指定し、そのフィールド名は、全要素のフィールド名と被りがない(= 一意である)必要があります。
言葉だと必要以上に難しくなってしまうので、下記のサンプルを参照してください。
OKとNGのケースを一覧で見てみると、特別難しくありませんね。判別共用体を値型として利用したいシーンはそこそこあるため、どういった使い方がダメなのかを知っておくと実装時に焦らずにすむでしょう。ここでの内容は忘れてもいいですが、こういった違いがあることは頭の片隅にでも置いておいてください。