From: Dave Thomas Date: 2000-03-16T14:02:54-06:00 Subject: [ruby-talk:01924] Re: Enumerations and all that. Well, I've thinking about enumerated types, and was wondering how they fit in to a typeless language. I came to the conclusion that there cannot be such things as free standing enumerated objects, because the semantics would be all wrong. If we have thing = Enum.new(:red, :green, :blue) Then how can we use it? We can have thing.set(:red) and thing.value #-> :red but does that help? Not really, because nothing stops you having thing = 'wombat' To my mind, the benefit of enumerated types comes from ensuring that the range of values taken by something is predetermined, and we can't do that with free variables. However, we probably don't want to do it with free variables. Instead, we want to make sure that the parameter to our Door#setState method is either :open or :closed. So it seems to make sense to have enumerated types associated with class attributes. So, I came up with the following. Here's a class Spigot with two enumerated type attributes: class Spigot enum :color, [ :red, :green, :blue ] enum :size, { :small => -1, :medium => 0, :large => 99} end The 'color' attribute has the external values :red, :green, and :blue. Because there are no associated internal values, they will be set to 0, 1, and 2. The 'size' attribute does have internal values associated with the three external ones. The external world can set and query the value of either of these object attributes: s = Spigot.new s.color = :red s.size = :large p s.color p s.size However, if you try to set an attribute using a value that's not associated with it, a TypeError is raised: s.color = :large #-> TypeError Finally, within class Spigot, you can get to the internal value of an attribute using _val. If the size is set to :large, size_val will return 99. class Spigot def printState printf "Internal value of color is #{color_val}\n" printf "Internal value of size is #{size_val}\n" end end I implemented 'enum' by extending class Module. It's a fairly ugly hack right now, but there's scope for it to be tidied... Anyway, here's all the code. It works under 1.5, but I'm not sure if the new Symbol stuff means it doesn't work under 1.4. I'd be interested in comments. Dave ############################ cut here ############################# class Module def enum(nameSym, vals) n = nameSym.id2name iVar = "_#{n}_var" keys = "K_#{n}_keys" class_eval "def #{n}() @#{iVar} end" class_eval "def #{n}=(val) puts #{keys}.inspect raise TypeError unless #{keys}.has_key?(val) @#{iVar} = val end" class_eval "def #{n}_val() #{keys}[@#{iVar}] end" class_eval "#{keys} = Hash.new" if Hash === vals vals.each_pair do |key, val| class_eval "#{keys}[#{key}] = #{val}" end else vals.each_index do |i| class_eval "#{keys}[#{vals[i]}] = #{i}" end end end end ################################################################### class Spigot enum :color, [ :red, :green, :blue ] enum :size, { :small => -1, :medium => 0, :large => 99} def printState printf "Internal value of color is #{color_val}\n" printf "Internal value of size is #{size_val}\n" end end s = Spigot.new s.color = :red s.size = :large p s.color p s.size s.printState s.color = :large # fails