[Ruby] Ruby で列挙型

by tanabe on August 26, 2007

Ruby には言語の機能としての列挙型がない。

ちょっと使いたいことがあって、いくつかのパターンで列挙型ぽく振舞うものを書いてみた。

require 'forwardable'
require 'ostruct'

class Array
  def to_enum_hash(start_from = 0)
    keys = self.dup
    vals = Array.new(self.size) {|i| start_from.to_i + i }
    Hash[*keys.zip(vals).flatten]
  end
end

module Enumeration
  def Enumeration.new(*args)
    initial_number, ordered_symbols = parse(args)
    OpenStruct.new(ordered_symbols.to_enum_hash(initial_number)).freeze
  end

  def enum(*args)
    initial_number, ordered_symbols = parse(args)
    define_enum(ordered_symbols, initial_number, self)
  end

  private
  def parse(args)
    if args.last.is_a?(Hash)
      initial_number = args.pop[:start_from]
    end
    initial_number ||= 0
    ordered_symbols = args.map {|a| a.to_sym }
    [initial_number, ordered_symbols]
  end

  def define_enum(names, value, target)
    return if names.size == 0
    target.const_set(names.shift, value)
    define_enum(names, value.next, target)
  end
end

class Module
  include Enumeration
end

使い方はそれぞれこんなかんじ。

require 'enumeration'
FeeTable = [
  [1200, 600], # sex[:male]
  [800,  400]  # sex[:female]
]

class Sex; enum :Male, :Female; end
class Generation; enum :Adult, :Child; end
p FeeTable[Sex::Male][Generation::Child] #=> 600

sex = Enumeration.new :male, :female
generation = Enumeration.new :adult, :child
p FeeTable[sex.female][generation.adult] #=> 800


sex = [:male, :female].to_enum_hash
generation = [:adult, :child].to_enum_hash
p FeeTable[sex[:female]][generation[:child]] #=> 400

OpenStruct を使った実装が使っているときの見た目が一番 Ruby ぽいかなぁ。



この記事へのトラックバック
module TokenType EOS = -1 INT = 257 SYMBOL = 258 STRING = 259 : みたいな定数を定義していて、「Ruby で C の enum みたいなことをやるにはどうしたいいんだろう?」と思ったので、ネットで調べてみました。 ふむふむ。Ruby(の文法)自体にはそういう機能がないような
[ruby]C の enum(列挙)に相当することを Ruby でやるには【himesuke’s STEP-log】at June 13, 2008 02:16