
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