From: Jeff Rose Date: 2006-06-01T21:33:29+09:00 Subject: Re: An alternative to the class Foo < Struct.new(vars) idiom and SuperStruct Mauricio Fernandez wrote: > I wrote this a couple weeks ago > ( http://eigenclass.org/hiki.rb?struct-alike+class+definition ) and was > considering whether it deserves an actual release. The implementation is > simple; taking just 40 lines of code, it allows you to do stuff like > > class MyClass < SuperClass.new(:a, :b) # doubly appropriate :) > def sum > @a + @b > end > end > a = MyClass.new(1, 1) > a # => # > a.sum # => 2 > > The generated class uses normal instance variables unlike Struct.new, and > creates the accessors for you. > > You also get keyword arguments for free: > > b = MyClass.new :a => 1, :b => 41 > b.sum # => 42 > b # => # > b.b # => 41 > > Default values are handled as follows: > > class Foo < SuperClass.new(:text, :times) { @times ||= 2 } > def greeting > (["Hello, #{@text}"] * times).join("\n") > end > end > > Foo.new("SuperClass", 2).greeting # => "Hello, SuperClass\nHello, SuperClass" > Foo.new(:text => "world").greeting # => "Hello, world\nHello, world" > > > > Unlike Struct.new, you can use SuperClass to generate classes in the middle > of the inheritance chain: > > class X > attr_reader :foo > def initialize(foo = 1) > @foo = foo > end > end > > class Y < SuperClass.new(:bar, :baz, X) {@baz ||= 10; initialize_super(@baz + 1) } > def fubar; @foo + @baz end > end > > Y.new(10, 1).foo # => 2 > Y.new(:bar => 1).fubar # => 21 > > > I have an extended implementation that also creates #eql?, #hash and #== > methods with selectable semantics. > > Here's the basic implementation: > > > # Copyright (C) 2006 Mauricio Fernandez http://eigenclass.org > # Use and distribution under the same terms as Ruby. > class SuperClass > def self.new_class(accessor_type, *args, &block) > parent = args.pop if Class === args.last > parent ||= Object > unless args.size > 1 > raise ArgumentError, "No point in using SuperClass for a single argument!" > end > Class.new(parent) do > @initialize_args = args.map{|x| "@#{x}".intern} > class << self; attr_reader :initialize_args end > case accessor_type > when :ro : attr_reader(*args) > when :rw : attr_accessor(*args) > end > > define_method(:initialize) do |*a| > args.each{|name| instance_variable_set("@#{name}", nil) } > if a.size == 1 && Hash === a[0] > args.each{|name| instance_variable_set("@#{name}", a[0][name.to_sym])} > elsif a.size != args.size > raise ArgumentError, > "wrong number of arguments (#{a.size} for #{args.size})" > else > args.each_with_index{|name, i| instance_variable_set("@#{name}", a[i])} > end > instance_eval(&block) if block > end > > if block > super_meth = parent.instance_method(:initialize) > define_method(:initialize_super){|*a| super_meth.bind(self).call(*a) } > private :initialize_super > end > end > end > > def self.new(*args, &block); new_class(:ro, *args, &block) end > def self.new_rw(*args, &block); new_class(:rw, *args, &block) end > end > I'm sold. This will be great for easily defining event types in EventMachine. Why the complicated default argument values though? For simple cases how about just using hash syntax: MyClass = SuperClass.new(:a => 2, :b => 3)