列挙体の概要
列挙体は列挙型(= enum)とも呼ばれ、各ラベルに整数型の値が必ず割り当てられている型です。コードの保守性を高めるために、数字リテラルの代わりに列挙体を使います。
// ------------------
// [ 構文 ]
// ------------------
type enum-name =
| value1 = integer-literal1
| value2 = integer-literal2
...
列挙体は、整数値しか指定できない という点を除けば単純な値を持つ判別共用体によく似ています。値は通常、0 または 1 から始まる整数(= 10進法)で指定されますが、bitで表現した整数値(= 2進法)で指定しても構いません。列挙体をビットフラグとして扱いたい場合は、[<Flags>]属性を指定する必要があります。
また、整数値リテラルを明示的に指定した場合、判別共用体とは違い、列挙体の型名を修飾子として使用しなければなりません。つまり、enum-name.value1 のように指定しなければならないということです。この振舞いは判別共用体のそれとは異なります。これは、列挙体は常に[<RequireQualifiedAccess>]属性があるためです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 成功と失敗の2つの値を定義した列挙体. | |
// 明示的に整数値リテラルを指定しています. | |
type State = | |
| Success = 100 | |
| Fail = 101 | |
// 明示的に整数値リテラルが指定されているため, | |
// 必ず State.Success と指定する必要があります. | |
let success = State.Success | |
// 以下はエラーとなります. | |
// 正しく指定するには State.Fail と指定しなければなりません. | |
let fail = Fail | |
前述したとおり、明示的に整数値リテラルを指定した場合は列挙体の型名を修飾子として使用しなければならないことがサンプルコードよりわかると思います。
では、明示的に指定しなかった場合はどうなるのでしょうか?答えは簡単で、判別共用体と同じように修飾子を付けても付けなくてもよくなります。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 成功と失敗の2つの値を定義した列挙体. | |
// 明示的に整数値リテラルを指定していません. | |
type State = | |
| Success | |
| Fail | |
// 明示的に整数値リテラルが指定されていないため, | |
// Success と指定しても State.Success と指定しても問題ありません. | |
let success1 = Success | |
let success2 = State.Success | |
// State.Fail の場合も同様です. | |
// 以下はどちらも問題なくコンパイルできます. | |
let fail1 = Fail | |
let fail2 = State.Fail | |
サンプルでみる列挙体
前節で列挙体の構文を紹介しつつ、簡単な利用法を紹介しました。本節では簡易的なサンプルを利用して、実際の値と構文の説明がどう対応しているかを見ていきたいと思います。
今回は色の種類を表す Color型 を使って列挙体を見ていきましょう。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Color は 赤・緑・青 という3つのケースを持つ列挙体です. | |
type Color = | |
| Red = 0 | |
| Green = 1 | |
| Blue = 2 | |
// 以下のいずれかの方法で各ケースの値を作成することができます. | |
let red = Color.Red | |
let green : Color = Color.Green | |
// 明示的に整数値リテラルを指定しているので, 以下の方法では値を作成できません. | |
let blue = Blue | |
下記の表は内包している要素と構文の要素名とを対応付けたものとなります。ただし重複する内容のものに関しては省略しています。
要素名 | 対応するColor型の要素 |
---|---|
type-name | Color |
value1 | Red |
integer-literal1 | 0 |
列挙体は構文通り、宣言すること自体もシンプルで簡単ですね。[ type-name ] に列挙体の名前を宣言し、[ value ] に列挙体に属する要素を宣言すればよいわけですね。[ integer-literal ] はオプションですので記述してもしなくても良いですが、整数値でないといけないことに注意が必要です。つまり、今回の Color型 には Red / Green / Blue という 3つ の要素が含まれていることがわかります。そして、その各要素は Red = 0 / Green = 1 / Blue = 2 という数値と結びついているわけです。
Color型の値を作成することも非常に簡単です。サンプルの9~10行目のように、列挙体の型名を修飾子にして、各要素を指定していることがわかります。整数値リテラルを明示していなければ、列挙体の型名を修飾子にする必要はありませんが、場合分けで覚えようとすると覚えることが増えてしまうだけなので、列挙体は必ず型名の修飾子をつけるようにしてしまった方が楽だと思います。また、そのようにすることでコードに統一感が生まれて、見た目に美しいコードになりやすいです。
利用例
列挙体はある種の状態を表すためによく使われます。特に "HTTPステータス" などの数値と状態が結びついているようなものと親和性が高いです。
今回の利用例では前節で利用した Color型 を少し改良したものにします。改良といっても、値リテラルを16進数のRGB表記にしただけです。この節では「列挙体とパターンマッチ」や「整数値の取得」などの話題を出しますが、これは Color型 特有のものではありません。列挙体全体に関係する話ですので、列挙体の使い方を忘れたらこのページに戻ってきてみてください。
以下は今回のサンプルである Color型 と、簡易的な利用法のサンプルとなります。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Color型の各要素を 16進数 で宣言 | |
type Color = | |
| Red = 0xFF0000 | |
| Green = 0x00FF00 | |
| Blue = 0x0000FF | |
// Color型に合わせて色を表示する関数 | |
let showColor color = match color with | |
| Color.Red -> printfn "Red!!" | |
| Color.Green -> printfn "Green!!" | |
| Color.Blue -> printfn "Blue!!" | |
| _ -> printfn "Undefined Color..." | |
// Color型の値をRGB値に変換する関数 | |
let getRgb (color:Color) = | |
let r = ((int color) &&& 0xFF0000) >>> 16 // 16bit分 右シフト | |
let g = ((int color) &&& 0x00FF00) >>> 8 // 8bit分 右シフト | |
let b = ((int color) &&& 0x0000FF) >>> 0 | |
(r, g, b) | |
showColor Color.Red | |
showColor Color.Green | |
showColor Color.Blue | |
// | |
// output: | |
// Red!! | |
// Green!! | |
// Blue!! | |
// | |
printfn "Color.Red= %A" (getRgb Color.Red) | |
printfn "Color.Green= %A" (getRgb Color.Green) | |
printfn "Color.Blue= %A" (getRgb Color.Blue) | |
// | |
// output: | |
// Color.Red = (255, 0, 0) | |
// Color.Green= ( 0, 255, 0) | |
// Color.Blue = ( 0, 0, 255) | |
// | |
列挙体とパターンマッチ
列挙体は value要素 を利用してパターンマッチさせることができます。サンプルの 8~12行目 でまさしく各ケースに応じた処理を実行させています。showColor関数は引数に列挙体の Color型 を取り、その Color型 の各ケースでパターンマッチしています。パターンマッチをする際に何か特別なことをする必要はありません。しかし、0から整数値が振られていない場合は網羅性の問題で警告(= warning)が発生してしまうため、サンプルのようにColor型の定義以外の値が入ってきた場合の処理を記述する必要があります。
整数値の取得
列挙体の概要 でサラッと紹介したように、列挙体の各ケースには必ず整数値が割り振られています。そのため、各ケースからそのケースに割り振られている整数値を取得することも可能です。サンプルの 16~18 行目を見てみましょう。シフト演算子が行われていますが、そこは無視して結構です。重要なのは "(int color)" の部分です。これは通常のキャストです。つまり通常の値と何ら変わらない方法で整数値を取得できるわけですね。
列挙体は利用することも宣言することも非常に容易ですが、とても強力な機能です。おそらくプログラムを作成していく上で必ずと言っていいほど頻繁に登場するので、ぜひ使いこなせるようになりましょう。