<5> 列挙型と部分範囲型 (標準 Pascal 範囲内での Delphi 入門)

# 5. 列挙型と部分範囲型

**クラシック Pascal** では列挙型は **スカラ型という型**として定義されていました。

さらに標準 Pascal の[単純型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b)が**スカラ型という分類**として紹介されており、[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B-%E9%A0%86%E5%BA%8F%E5%9E%8B)と[実数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#25-real-%E5%9E%8B)が分離されていなかったため、[実数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#25-real-%E5%9E%8B)の説明がちょっと苦しいものになっていました。次の文は『J&W』第2版からの引用です。
> 実数はスカラ型に含めているが、必ずしも他のスカラ型と同じ意味で使用できるとは限らない。特に, 関数 pred と succ は実数型の引数をとることはできないし, 配列要素の指定や for 文の制御, あるいは集合の基底の型 (第8章参照) の定義に実数型の値を使用する事はできない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/a344da80-f856-53a3-fe6d-4a6596afb76c.png)

一般的にスカラ型というのは**大小比較が可能な型**の事です。その意味では文字も文字列も**スカラ値を持つ型**であるので、**スカラ型という型**を作ってしまうと混乱する事が予想されます。そして実際に混乱したのでしょう、標準 Pascal からはスカラ型の説明が消えています。

標準 Pascal では型としてのスカラ型 (狭義のスカラ型) は列挙型に相当し、分類としてのスカラ型 (広義のスカラ型) は[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B-%E9%A0%86%E5%BA%8F%E5%9E%8B)に相当します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/3f456b4e-93e7-94a7-5fb2-bd8ad33f6b18.png)

## 5.1. 列挙型 (Enumerated Types)
**列挙型** は値を表す識別子を並べた値の集合で、プログラマ定義の型 [^1] です。

```
列挙型 = 
　"(" 識別子 {"," 識別子} ")".
```

最初の識別子は 0、その次は 1 というように順序値を持ちます。例えば曜日を表す TDayOfTheWeek という列挙型は次のように定義できます。

```pascal:EnumTest.pas
program EnumTest(output); 
type
  TDayOfTheWeek = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);  
var
  d: TDayOfTheWeek;
begin
  for d:=Mon to Sun do
    Writeln(Ord(d));
end. 
```

[Ord()](http://docwiki.embarcadero.com/Libraries/ja/System.Odd) 関数は[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B-%E9%A0%86%E5%BA%8F%E5%9E%8B)の順序値を返すのでしたね。

```pascal
type
  TDayOfTheWeek = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);  
  TPokemon = (Sun, Moon);  
```

続けて TPokemon のような列挙型を定義する事はできません。`Sun` という識別子が重複しているからです。

型を再利用しないのであれば、列挙型の定義と同時に変数を宣言する事が可能です。

```pascal:EnumTest2.pas
program EnumTest2(output);
var
  DayOfTheWeek: (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
begin
  DayOfTheWeek := Wed;
  Writeln(Ord(DayOfTheWeek));
end.
```

使い所が難しいのですが、Delphi では列挙型の値を列挙型とインデックス値で指定できます。

```pascal:EnumTest3.pas
program EnumTest3(output); 
{$APPTYPE Console}
type
  TDayOfTheWeek = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
var
  d: TDayOfTheWeek;
begin
  d := TDayOfTheWeek(2);
  Writeln(Ord(d));
end.
```

上の例では 2 と表示されます。当たり前といえば当たり前ですが。また、Delphi では明示的に値の順序値を設定する事もできます。

```pascal
type
  TDayOfTheWeek = (Mon = 1, Tue = 2, Wed = 3, Thu = 4, Fri = 5, Sat = 6, Sun = 7);  
```

この場合の TDayOfTheWeek(2) の値も 2 になります。3 ではありません。

Boolean 型を列挙型で定義してみると次のようになります。

```pascal
type
  Boolean = (False, True);  
```

確かに[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B-%E9%A0%86%E5%BA%8F%E5%9E%8B)は **スカラ型 (列挙型)** で定義する事が可能なので、**クラシック Pascal** の説明が完全に間違っている訳ではありません。

しかしながら、**クラシック Pascal** では Real は実数の部分集合という扱いになっているのです。**スカラ型 (列挙型)** ではどうやっても[実数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#25-real-%E5%9E%8B)を定義できないと思うのですが...。

**See also:**

 - [列挙型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%8D%98%E7%B4%94%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E5.88.97.E6.8C.99.E5.9E.8B)

### (5.1.1) スコープのある列挙型 (Scoped Enums)

Delphi 2009 以降で追加された `{$SCOPEDENUMS ON}` 指令を使うと、**スコープのある列挙型**を作る事ができ、異なる列挙型で同じ列挙型要素を定義可能となります。

```pascal
type
  TDayOfTheWeek = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
{$SCOPEDENUMS ON}
  TPokemon = (Sun, Moon); // Sun が使える
```

スコープのある列挙型では、列挙型要素を型名によって修飾する必要があります。

```pascal
var
  a: TDayOfTheWeek;
  b: TPokemon;
begin
  a := Sun;
  b := TPokemon.Sun;
  b := Moon; // エラー
end.
```

**See also:**

 - [スコープのある列挙型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%8D%98%E7%B4%94%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E3.82.B9.E3.82.B3.E3.83.BC.E3.83.97.E3.81.AE.E3.81.82.E3.82.8B.E5.88.97.E6.8C.99.E5.9E.8B)
 - [$SCOPEDENUMS 指令 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%81%AE%E3%81%82%E3%82%8B%E5%88%97%E6%8C%99%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)

### (5.1.2) 列挙型定数とグローバル列挙型変数の初期化

Delphi では次のような列挙型定数が使えます。

```pascal
program EnumConstantsTest;

{$APPTYPE CONSOLE}

// グローバル列挙型定数
const
  DayOfTheWeek1: (Mon, Tue, Wed, Thu, Fri, Sat, Sun) = Sun;

// グローバル列挙型変数
var
  DayOfTheWeek2: (Mon2, Tue2, Wed2, Thu2, Fri2, Sat2, Sun2) = Sun2;

  procedure Sub;
//var
//  ローカル列挙型変数は初期化できない
//  DayOfTheWeek2: (Mon3, Tue3, Wed3, Thu3, Fri3, Sat3, Sun3) = Sun3;
  begin
    ...
  end; { Sub }
begin
  ...
end.
```

但し、列挙型の型付き定数にあまり意味があるとは思えません。


## 5.2. 部分範囲型 (Subrange Types)
**部分範囲型** は他の[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B-%E9%A0%86%E5%BA%8F%E5%9E%8B)の値の部分集合で、プログラマ定義の型 [^1] です。

```
部分範囲型 = 
　定数 ".." 定数.
```

値の範囲を `..` で指定します。

```pascal
type 
  TDayOfTheWeek = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);  
  TBool = False..True;   (* 論理型の部分範囲型 *)
  TDice = 1..6;          (* 整数型の部分範囲型 *)
  TAlphabet = 'A'..'Z';  (* 文字型の部分範囲型 *)
  TWorkDays = Mon..Fri;  (* 列挙型の部分範囲型 *)
```

標準 Pascal では符号なし整数型は用意されていませんが、次のようにして Byte 型を定義できます。

```pascal
type
  Byte = 0..255;
```

例えば次のようなコードは Delphi だと範囲チェックが行われるためコンパイル時にエラーになります。

```pascal:SubrangeTest.pas
program SubrangeTest(output); 
type
  TDice = 1..6;
var
  D: TDice;
begin
  D := 7;     (* Error! *)
  Writeln(D);
end.
```
型を再利用しないのであれば、部分範囲型の定義と同時に変数を宣言する事が可能です。

```pascal:SubrangeTest2.pas
program SubrangeTest2(output);
var
  Dice: 1..6;
begin
  Dice := 6;
  Writeln(Dice);
end.
```

Delphi では部分範囲型の定数に式が使えます。

```pascal
var
  TRange: 0..1024 - 1;
```

**See also:**

 - [部分範囲型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%8D%98%E7%B4%94%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E9.83.A8.E5.88.86.E7.AF.84.E5.9B.B2.E5.9E.8B)


### (5.2.1) 部分範囲型定数とグローバル部分範囲型変数の初期化

Delphi では次のような部分範囲型定数が使えます。

```pascal
program SubrangeConstantsTest;

{$APPTYPE CONSOLE}

// グローバル部分範囲型定数
const
  Dice1: 1..6 = 6;

// グローバル部分範囲型変数
var
  Dice2: 1..6 = 6;

  procedure Sub;
//var
//  ローカル部分範囲型変数は初期化できない
//  Dice3: 1..6 = 6;
  begin
     ...
  end; { Sub }
begin
  ...
end.
```

但し、部分範囲型の型付き定数にあまり意味があるとは思えません。


# 索引
[ [← 4. 動作の概念](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5) ] [ [→ 6. 構造型の概要と配列型](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac) ] [:sushi:](https://qiita.com/ht_deko/items/e581e8f5c0c32d5a58e2)

[^1]: `プログラマ定義の型` は ISO/IEC 7185 の構文定義では **new-type**、JIS X 3008 では **書下(くだ)し型** と記述されています。『J&W』の構文定義には記述がありません。