LoginSignup
15
3

Rubyで列挙型(enum)っぽく連番を定義できるgemを作ってみた

Posted at

はじめに:列挙型について

C言語には列挙型(enum)があります。列挙型を使うと0から始まる連番を定数として管理できます。

#include <stdio.h>

enum
{
    E_COLOR_RED,    // 0
    E_COLOR_YELLOW, // 1
    E_COLOR_GREEN,  // 2
};

int main()
{
    printf("E_COLOR_RED = %d\n", E_COLOR_RED);
    printf("E_COLOR_YELLOW = %d\n", E_COLOR_YELLOW);
    printf("E_COLOR_GREEN = %d\n", E_COLOR_GREEN);

    return 0;
}

実行結果

E_COLOR_RED = 0
E_COLOR_YELLOW = 1
E_COLOR_GREEN = 2

REDとYELLOWの間にBLUEを追加した場合、自動的に採番し直されます。

#include <stdio.h>

enum
{
    E_COLOR_RED,
    E_COLOR_BLUE, // Add
    E_COLOR_YELLOW,
    E_COLOR_GREEN,
};

int main()
{
    printf("E_COLOR_RED = %d\n", E_COLOR_RED);
    printf("E_COLOR_BLUE = %d\n", E_COLOR_BLUE);
    printf("E_COLOR_YELLOW = %d\n", E_COLOR_YELLOW);
    printf("E_COLOR_GREEN = %d\n", E_COLOR_GREEN);

    return 0;
}

実行結果(1、2だったYELLOWとGREENはそれぞれ2、3に変わる)

E_COLOR_RED = 0
E_COLOR_BLUE = 1
E_COLOR_YELLOW = 2
E_COLOR_GREEN = 3

Javaにも列挙型はある

列挙型はC言語のみならず、いろいろな言語で実装されています。たとえば以下はJavaで列挙型を使うコード例です。

public class Sample {
    enum Color {
        RED,    // 0
        YELLOW, // 1
        GREEN   // 2
    }

    public static void main(String[] args) {
        System.out.println("Color.RED = " + Color.RED.ordinal());
        System.out.println("Color.YELLOW = " + Color.YELLOW.ordinal());
        System.out.println("Color.GREEN = " + Color.GREEN.ordinal());
    }
}

実行結果

Color.RED = 0
Color.YELLOW = 1
Color.GREEN = 2

C# にもある

C# でもほぼ同じコードが書けます。

using System;

public class Sample
{
    enum Color
    {
        RED,    // 0
        YELLOW, // 1
        GREEN   // 2
    }
    
    public static void Main(string[] args)
    {
        Console.WriteLine("Color.RED = {0}", (int)Color.RED);
        Console.WriteLine("Color.YELLOW = {0}", (int)Color.YELLOW);
        Console.WriteLine("Color.GREEN = {0}", (int)Color.GREEN);
    }
}

実行結果

Color.RED = 0
Color.YELLOW = 1
Color.GREEN = 2

でもRubyには列挙型がない!

しかし、Rubyには列挙型がありません。もしやるとするなら、愚直に定数を定義することになります。

RED = 0
YELLOW = 1
GREEN = 2

puts "RED = #{RED}"
puts "YELLOW = #{YELLOW}"
puts "GREEN = #{GREEN}"

しかし、これだとBLUEを追加したときにYELLOWとGREENの修正も必要になります。

 RED = 0
+BLUE = 1
-YELLOW = 1
-GREEN = 2
+YELLOW = 2
+GREEN = 3

次のように工夫すれば修正量を少し減らすことができますが、あまりスマートではありません。

RED = 0
BLUE = RED + 1
YELLOW = BLUE + 1
GREEN = YELLOW + 1

そこで、enumっぽく連番を定義できるgemを作ってみた

そこで、Rubyでもenumっぽく連番を定義できるgemを作ってみました。
seq_as_enum というgemです。

インストール方法はふつうのgemと同じです。

gem install seq_as_enum

このgemはこんなふうに使います。

require 'seq_as_enum'

class Sample
  extend SeqAsEnum

  # seq_as_enumメソッドで連番を定義
  seq_as_enum :RED, :YELLOW, :GREEN
  # すると、以下のような定数が定義される
  # RED = 0
  # YELLOW = 1
  # GREEN = 2

  # 上で定義した連番を使用する
  def self.main
    puts "RED = #{RED}"
    puts "YELLOW = #{YELLOW}"
    puts "GREEN = #{GREEN}"
  end
end

Sample.main

実行結果

RED = 0
YELLOW = 1
GREEN = 2

このgemを使えば、REDとYELLOWの間にBLUEを追加するときも必要最小限の修正で済みます。

require 'seq_as_enum'

class Sample
  extend SeqAsEnum

  # REDとYELLOWの間にBLUEを追加する
  seq_as_enum :RED, :BLUE, :YELLOW, :GREEN

  def self.main
    puts "RED = #{RED}"
    puts "BLUE = #{BLUE}"
    puts "YELLOW = #{YELLOW}"
    puts "GREEN = #{GREEN}"
  end
end

Sample.main

実行結果(1、2だったYELLOWとGREENはそれぞれ2、3に変わる)

RED = 0
BLUE = 1
YELLOW = 2
GREEN = 3

応用的な使い方

このgemにはいくつか応用的な使い方があります。

初期値を変える

0以外の値からスタートしたい場合はinitオプションを使います。

seq_as_enum :RED, :YELLOW, :GREEN, init: 10
# 以下の定数を定義したのと同じ
# RED = 10
# YELLOW = 11
# GREEN = 12

文字列で連番を作る

A, B, Cのような連番を作ることもできます。

seq_as_enum :RED, :YELLOW, :GREEN, init: 'A'
# 以下の定数を定義したのと同じ
# RED = 'A'
# YELLOW = 'B'
# GREEN = 'C'

prefixを付ける

prefixオプションでprefixを付けることもできます。

seq_as_enum :RED, :YELLOW, :GREEN, prefix: :COLOR
# 以下の定数を定義したのと同じ
# COLOR_RED = 0
# COLOR_YELLOW = 1
# COLOR_GREEN = 2

Javaのenumのように型.定数の形式にする

data_asオプションを付けると、Javaのenumのように型.定数の形式で連番が定義できます。

seq_as_enum :RED, :YELLOW, :GREEN, data_as: :Color
# 型.定数の形式で連番が定義できる
Color.RED    #=> 0
Color.YELLOW #=> 1
Color.GREEN  #=> 2

利用例

以下のコードは、HTMLをパースしてテーブル内の各列の内容を画面に出力するコード例です。
各列には以下のようなデータが入力されているものとします。

  • 0番目 = 名前
  • 1番目 = 住所
  • 2番目 = 電話番号

ここでは td[0] td[1] td[2] のように列番号をハードコードする代わりに、seq_as_gem で定義した定数を使って td[NAME] td[ADDRESS] td[PHONE] のようにして各列の値を読み取っています。

require 'seq_as_enum'
require 'nokogiri'

# パース対象のHTML
html = <<HTML
<table>
  <thead>
    <tr>
      <th>名前</th><th>住所</th><th>電話</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice</td><td>東京</td><td>090-1234-5678</td>
    </tr>
  </tbody>
</table>
HTML

class TableParser
  extend SeqAsEnum

  # 連番を定数として定義
  seq_as_enum :NAME, :ADDRESS, :PHONE

  def self.parse(html)
    doc = Nokogiri::HTML(html)
    doc.css('tbody tr').each do |tr|
      td = tr.css('td').map(&:text)
      # td[0]、td[1]、td[2]のように列番号をハードコードする代わりに定数を使う
      puts "name: #{td[NAME]}, address: #{td[ADDRESS]}, phone: #{td[PHONE]}"
    end
  end
end

TableParser.parse(html)
#=> name: Alice, address: 東京, phone: 090-1234-5678

まとめ

というわけで、この記事ではRubyで列挙型(enum)っぽく連番を定義できる、seq_as_enum というgemを紹介してみました。

「なんでRubyには列挙型がないんだー!」と困っていた方はこちらのgemを試してみてください。

15
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
3