Mailing List Archive
tlug.jp Mailing List tlug archive tlug Mailing List Archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]Re: [tlug] ruby and python in Japan
- Date: Thu, 8 Mar 2007 00:30:59 +0900 (JST)
- From: Curt Sampson <cjs@??>
- Subject: Re: [tlug] ruby and python in Japan
- References: <45E16CA8.2010909@example.com> <20070225122334.GA10626@example.com> <87r6sdk5o7.fsf@example.com>
On Mon, 26 Feb 2007, Stephen J. Turnbull wrote:
What's the use-case for changing a class on the fly?
Sorry about taking so long to generate a reply on this.
Most of the talk about changing classes on the fly has been related to monkey-patching, but that's not, in my experience, the common use case for changing classes at run-time in Ruby. It's far more often used for a form of meta-programming.
I should first make it clear that changing class definitions at run-time is actually the only form of class definition in Ruby: when you define a class, the statements in that definition are executed at runtime. So, for example, this will print out Hello to the standard output:
class Foo puts("Hello.") end
The 'class Foo' bit defines (if it's not already defined) a constant Foo, creates a new object of class Class, assigns it to that constant, and then leaves you (until you get to the 'end' statement) in a context where statements and expressions are evaluated in the context of that class:
class Bar puts("Self class: #{self.class} Self.inspect: #{inspect}") puts("Contains baz: #{instance_methods.include?('baz')}") def baz; "abc"; end puts("Contains baz: #{instance_methods.include?('baz')}") end
produces:
self class: Class self.inspect: Bar Contains baz: false Contains baz: true
(Module#instance_methods [Module is the superclass of Class] returns a list of the methods defined in that module. Don't confuse it with Object#methods, which returns the list of methods that that particular object responds to.)
So this is really just syntatic sugar for
Bar = Class.new Bar.send(:define_method, :baz, lambda { "abc" }) puts(Bar.new.baz)
which produces
abc
(I use send to call Bar#define_method because it's a private method.)
A very frequent use of this is the attr_* methods, which are actually just functions that module responds to, which create new methods in the module in which they're invoked:
class Quux puts(instance_methods.select { |m| /^xy/.match(m) }.inspect) attr_accessor :xyzzy puts(instance_methods.select { |m| /^xy/.match(m) }.inspect) end
produces:
[] ["xyzzy", "xyzzy="]
I've used this technique to create a DSL for SWF parsing in Starling's RSWF library. Here are some incomplete fragments to demonstrate:
class SWFData
def self.swfio(io_object) ... self.const_set(:IO_OBJECTS, []) unless self.const_defined?(:IO_OBJECTS) self::IO_OBJECTS << io_object io_object.setup_data_class(self) end
...
end
class RGBA ... end
class RGBA_IO
def setup_data_class(data_class) data_class.instance_eval { attr_accessor :color } end
def clear_data(data) data.color = RGBA.new(0xffffff, 0xff) end
def read_data(data, stream) data.color = RGBA.new(stream.read_ubyte << 16 | stream.read_ubyte << 8 | stream.read_ubyte, stream.read_ubyte) stream.log { ' rgba=' + data.color.inspect } end
...
end
class BackgroundColor < Element tag_number 9 swfio Struct::RGB_IO.new end
What's happening here is that, when we define BackgroundColor, we call swfio with an object that knows how to do IO for the color component. (This is a very simple example; typically there would be a series of swfio statements for the various components of a tag.) The swfio function adds an IO_OBJECTS constant to the BackgroundColor class, sets it to an empty array, and appends the given IO object to that array. It then asks the IO object to further define the class, and the RGBA_IO creates 'color' and 'color=' methods on the BackgroundColor class. Later, when given an instance of the BackgroundColor class, it uses those methods to store the data that it reads and retrieve the data that it writes, which would be in the form of an RGBA object. (For many of the simpler fields in a tag, this would be just a number or a String.)
The class definition done by an IO object gets more complex. Some IO objects take parameters to determine the name of the accessors:
class DefineEditText < Definition tag_number 37 swfio Struct::ID_IO.new swfio Struct::Rectangle_IO.new(:bounds) swfio Struct::Flags_IO.new( :has_text, :word_wrap, :multiline, :password, :readonly, :has_color, :has_max_length, :has_font, :reserved1, :auto_size, :has_layout, :no_select, :border, :reserved2, :html, :use_outlines) swfio Struct::RefID_IO.new swfio Struct::UShort_IO.new(:font_height) swfio Struct::RGBA_IO.new swfio Struct::UShort_IO.new(:max_length) ... swfio Struct::String_IO.new(:variable_name) swfio Struct::String_IO.new(:initial_text) ... end
Others will define a number of new special-purpose methods on the class being created:
module JPEGOperations # Various method definitons here end
class JPEGSegments_IO < List_IO ... def setup_data_class(data_class) super data_class.instance_eval { include JPEGOperations } end ... end
I hope that this isn't too confusing. I don't think it's exactly the clearest and easiest way anybody's ever come up with to do this sort of thing, myself, which is one of the reasons I'm tending away from Ruby these days.
cjs -- Curt Sampson <cjs@??> +81 90 7737 2974
Home | Main Index | Thread Index
- Prev by Date: Re: [tlug] Using tar to archive files
- Next by Date: Re: [tlug] (OT) Reverse DNS resolution blues- ISP recommendations?
- Previous by thread: Re: [tlug] ruby and python in Japan
- Next by thread: [tlug] Goodness of Ruby over Python (or not)
- Index(es):
Home Page Mailing List Linux and Japan TLUG Members Links