Ruby の Enum クラス

列挙型クラス。

C# の列挙型とほぼ同じ動きをする物を作ってみました。 メソッドなどはStructクラスを 参考にしています。

何種類かの値のみを持つクラスを作成したい場合に使うと幸せになれるかもしれません。 Symbol クラスでは自分でその実際の値を指定できませんが、このクラスの場合は こちらで指定することが出来ます。

Symbol クラスとの比較

Symbol Enum
値の定義 :Foo Enum::XXX.define( :Foo, val )
値のクラス Symbol Enum::XXX
値を得る :Foo, "Foo".intern Enum::XXX::Foo, Enum::XXX.new( val )
名前の取得 id2name, to_s name, to_s
実際の値 to_i value
実際の値の決定方法 Ruby が決定 自前(省略した場合は自動で名前の Symbol )

スーパークラス:

クラスメソッド:

Enum.new( [ [ name, ] entry ... ] )

Enum クラスに name という名前の新しいサブクラスを作って、それを返します。 サブクラスでは列挙型のメンバがそのクラスの定数として定義されています。例えば

cat = Enum.new( "Cat", :Noraneko, :Nameneko, :Doraemon )
printf "name:%s", cat::Noraneko.name

は "name:Noraneko" を出力します。

列挙型名 name は Enum のクラス定数名になりますので大文字で始まる必要があります。 entry は、Symbol か文字列で指定します。これらは作成する列挙型クラスの定数名になりまので 大文字で始まる必要があります。

name を省略した場合(第一引数が文字列以外の場合)、生成した構造体クラスは名前のないクラスとなります。 名前のないクラスは、最初に名前を求める際に代入されている定数名を検索し、 見つかった定数名をクラス名とします( Class.new を参照)。

例: 列挙型クラスのクラス名

p Enum.new( "Foo", :Bar, :Baz )
=> Enum::Foo

p Foo = Enum.new( :Bar, :Baz )
=> Foo

entry が存在する場合 それぞれを引数に作成された 列挙型クラスの define メソッドを呼び出します。

作成される列挙型クラスは Enumerable が extend されています。

列挙型クラスのクラスメソッド:

Enum::XXX.new( value )
Enum::XXX[value]

列挙型のインスタンスを中身を value で作成します。

列挙型に定義されていない value を

Enum::XXX.define( name [, value ] )
Enum::XXX.define( array )
Enum::XXX.define( hash )

最初の形式は、 文字列 name の名前を持つ列挙型を定義します。 value が存在する場合はその名前の列挙型の実際の値を value にします。

定義された列挙型値は Enum::XXX に名前が name の定数になりますので、 大文字で始まる必要があります。

Array が指定された場合その中のそれぞれを define メソッドの第一引数として呼び出します。 Hash の場合はキーを第一引数に、値を第二引数として define メソッドを呼び出します。

Enum::XXX.member?( value )

value がこの列挙型に定義されている場合は真、されていない場合は偽を返却します。

Enum::XXX.members

この列挙型に定義されている値の配列を返却します。

Enum::XXX.each{|member| ... }
Enum::XXX.each_member{|member| ... }

この列挙型に定義されているそれぞれの値でブロックを評価します。

Enum::XXX.size
Enum::XXX.length

この列挙型に定義されている値の数を返却します。

メソッド:

name
to_s

この列挙型インスタンスの名前を取得します。

value

この列挙型インスタンスの実際の値を取得します。

proper?

この列挙型インスタンスが定義されている値を持っているかどうかをチェックします。 定義されている場合は真、されていない場合は偽を返却します。

ソース

class Enum

  def self.new( arg1 = nil, *rest )
    name, entry =
    if ( arg1.is_a?( String ) or arg1.nil? )
      [arg1, rest]
    else
      [nil, rest.unshift( arg1 )]
    end
    
    klass = Class.new( self )
    self.const_set( name, klass ) unless name.nil?

    klass.instance_eval{|klass|
      self.extend( Enumerable )
      @index = {}
      @members = []

      def new( value )
        obj = self.allocate
        obj.__send__( :initialize, value )
        obj
      end
      alias :[]   :new

      def members
        @members.dup
      end

      def member?( value )
        @index.has_key?( value.is_a?( self ) ? value.value : value )
      end

      def define( arg1, arg2 = nil )
        if ( arg1.is_a?( Hash ) )
          arg1.each_pair{|name, value|
            self.define( name, value )
          }
        elsif ( arg1.is_a?( Array ) )
          arg1.each{|name|
            self.define( name )
          }
        else
          name = arg1
          value = if arg2.nil? then name.to_sym else arg2 end
          @members << self.new( value )
          self.const_set( name, @members.last )
          @index[value] = name.to_s.dup.freeze
        end
      end

      def each_member
        self.members.each{|one| yield( one ) }
      end
      alias :each :each_member

      def length
        self.members.length
      end
      alias :size :length
    }

    entry.each{|one| klass.define( one ) }
    klass
  end


  def initialize( value )
    @value = value
  end

  def hash
    @value.hash
  end

  def eql?( other )
    other.eql?( @value )
  end

  def ==( other )
    other == @value
  end

  def proper?
    self.class.member?( self.value )
  end

  def name
    self.class.instance_variable_get( :@index )[self.value]
  end
  alias :to_s :name

  def value
    @value
  end

  def inspect
    "\#<enum #{self.class} #{self.proper? ? self.name : nil.inspect}, #{self.value.inspect}>"
  end

end

if __FILE__ == $0
  Foo = Enum.new
  Foo.define( :Bar, 1 )
  Foo.define( :Baz )

  ## そのクラスの定数として登録される
  p Foo::Bar         # => #<enum Foo Bar, 1>
  p Foo::Bar.name    # => "Bar"
  p Foo::Bar.value   # => 1

  p Foo.new( 1 )     # => #<enum Foo Bar, 1>
  p Foo.member?( 1 ) # => true
  p Foo.member?( 2 ) # => false
  p Foo.members      # => [#<enum Foo Baz, :Baz>, #<enum Foo Bar, 1>]

  ## 存在しない値から作成できるが正しくない
  p Foo.new( 2 ).proper? # => false
  
  ## 他の定義の仕方
  ## Hash を使う
  Hoge = Enum.new( {
    :Foo => 100,
    :Bar => 200,
    :Baz => 300,
  } )

  ## Array を使う
  Fuga = Enum.new( [:Foo, :Bar, :Baz] )
  
  ## 可変長引数を使う
  Piyo = Enum.new(
    :Foo,
    :Bar,
    :Baz
  )

  [Hoge, Fuga, Piyo].each{|one| p one.members }

end