Design Advice: Sub-Class 'Instances'

G

Gavin Kistner

--Apple-Mail-2--468029632
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed

Summary
I'm looking for advice on how to design code so that I can dynamically
add/remove instantiable classes, where each of these classes have
values for a set of properties defined by a parent class.

Details
I'm starting to design/write an uber-ambitious home automation hub, in
Ruby. In addition to having convenient things like a web-based GUI
front-end, scheduler, and so on, the heart of this application is that
it will (eventually) work with any automation hardware whose
communication protocol is open (or reverse-engineered).

The plan is to have 'adaptors' for each discrete hardware type, which
encapsulate all the guts of the communication protocol and expose
methods for that bit of hardware. The user can then 'instantiate' one
or more of these adaptors, representing physical instances of that bit
of hardware in the home.

(For example, there's a single "Lutron RadioRA Wall Dimmer" adaptor,
but the user may have 4 such switches in the house, named "Front
Kitchen Lights", "Rear Kitchen Lights", "Bedroom Lights", and "Entry
Lights".)

In addition to custom methods, each adaptor needs to expose some common
information, such as its name, manufacturer, category, sub-category,
model number, and so on.

I initially decided to have code like this:

me = Foo::Bar::DeviceType.new( 'Lutron RadioRA Wall Dimmer', :Lutron,
:Switches, :Dimmers, 'RA-ND6' )
me.add_action( :turn_on, Proc.new{ ... } )
me.add_action( :turn_off, Proc.new{ ... } )
me.add_action( :level=, Proc.new{ ... } )
me.add_action( :level, Proc.new{ ... } )

#...and then later do something like...
Foo::Bar::add_device( 'Entry Lights', blah )
# ...where blah is a pointer to the DeviceType instance


But then I realized that I'm mostly just putting a wrapper around a
class definition. Further, I realized that I want all adaptors to
support common methods (name, manufacturer, model number), all light
switches to support some common methods (turn_on, turn_off), and all
dimmers to support others still (level= and level). This reeks of a
class hierarchy, so I was thinking that I might do something like:

# Core app
class Foo::Bar::DeviceType
def name; raise "Implement Me!"; end
def manufacturer; raise "Implement Me!"; end
def model; raise "Implement Me!"; end

class Light
def turn_on; raise "Implement Me!"; end
def turn_off; raise "Implement Me!"; end

class Dimmer
def level=(n); raise "Implement Me!"; end
def level; raise "Implement Me!"; end
end
end
end

#Adaptor file
class Foo::Bar::DeviceType::Light::Dimmer::RadioRA
def name; 'Lutron RadioRA Wall Dimmer'; end
def manufacturer; 'Lutron'; end
def name; 'RA-ND6'; end
def turn_on; ...; end;
def turn_off; ...; end;
def level=(n); ...; end;
def level; ...; end;
Foo::Bar::DeviceType::add_device( self )
end

... and the add_device method would
a) Create a dummy parent class and run through all the methods to see
if they throw errors.
b) Add the class into a list of instantiable adaptors if it works.

Questions
1) Is there a way to inspect the runtime state and figure out which
subclasses exist for a given class?
2) Is there a better way to force/detect if a subclass implements
certain methods?
3) Is there a better way overall to achieve my goal?


Thanks if you even read this far. Thanks even more if you take the time
to think about the problem and offer suggestions.
 
H

Hal Fulton

Gavin said:
I'm starting to design/write an uber-ambitious home automation hub, in
Ruby. In addition to having convenient things like a web-based GUI
front-end, scheduler, and so on, the heart of this application is that
it will (eventually) work with any automation hardware whose
communication protocol is open (or reverse-engineered).

This pleases me more than I can say.

I have a similar project which I outlined and began to code, but I ran
out of steam when I had problems debugging my X10 code.

See http://rubyhacker.com/domo.html

Let's see if we can develop some synergy or just plain merge these
projects.

I'm not picky about all the design details of "my" project; if I don't
have help with it, it will never materialize at all; and if I do have
help, the other person(s) will contribute to the design.
Questions
1) Is there a way to inspect the runtime state and figure out which
subclasses exist for a given class?

I think there's a hook by which you can have a callback keep a list. I
forget the name: inherited I think. ri seems incorrect on that one.
2) Is there a better way to force/detect if a subclass implements
certain methods?

Well... you might just use the callback technique again and maintain a
list of methods that are redefined.
3) Is there a better way overall to achieve my goal?

Dunno... for some thoughts on creating classes dynamically, you might
want to look at this snippet: http://rubyforge.org/snippet/detail.php?type=snippet&id=25

For a fancy plugin architecture (which you can even reuse), look at
FreeRIDE's thing called FreeBase.

Definitely let's talk further.

BTW: I like HomeSeer so much that if I could get Ruby scripting to work for
it, I would almost abandon the idea of a Linux solution. It is "supposed"
to work, but the last time I tried, I couldn't make it happen. If you're
interested in looking at that problem, let me know. I would *love* to get
that to work, because then I could start scripting my entire house in
Ruby *today*.


Thanks,
Hal
 
R

Robert Klemme

Gavin Kistner said:
Summary
I'm looking for advice on how to design code so that I can dynamically
add/remove instantiable classes, where each of these classes have
values for a set of properties defined by a parent class.

Details
I'm starting to design/write an uber-ambitious home automation hub, in
Ruby. In addition to having convenient things like a web-based GUI
front-end, scheduler, and so on, the heart of this application is that
it will (eventually) work with any automation hardware whose
communication protocol is open (or reverse-engineered).

The plan is to have 'adaptors' for each discrete hardware type, which
encapsulate all the guts of the communication protocol and expose
methods for that bit of hardware. The user can then 'instantiate' one
or more of these adaptors, representing physical instances of that bit
of hardware in the home.

(For example, there's a single "Lutron RadioRA Wall Dimmer" adaptor,
but the user may have 4 such switches in the house, named "Front
Kitchen Lights", "Rear Kitchen Lights", "Bedroom Lights", and "Entry
Lights".)

In addition to custom methods, each adaptor needs to expose some common
information, such as its name, manufacturer, category, sub-category,
model number, and so on.

I initially decided to have code like this:

me = Foo::Bar::DeviceType.new( 'Lutron RadioRA Wall Dimmer', :Lutron,
:Switches, :Dimmers, 'RA-ND6' )
me.add_action( :turn_on, Proc.new{ ... } )
me.add_action( :turn_off, Proc.new{ ... } )
me.add_action( :level=, Proc.new{ ... } )
me.add_action( :level, Proc.new{ ... } )

#...and then later do something like...
Foo::Bar::add_device( 'Entry Lights', blah )
# ...where blah is a pointer to the DeviceType instance


But then I realized that I'm mostly just putting a wrapper around a
class definition. Further, I realized that I want all adaptors to
support common methods (name, manufacturer, model number), all light
switches to support some common methods (turn_on, turn_off), and all
dimmers to support others still (level= and level). This reeks of a
class hierarchy, so I was thinking that I might do something like:

# Core app
class Foo::Bar::DeviceType
def name; raise "Implement Me!"; end
def manufacturer; raise "Implement Me!"; end
def model; raise "Implement Me!"; end

class Light
def turn_on; raise "Implement Me!"; end
def turn_off; raise "Implement Me!"; end

class Dimmer
def level=(n); raise "Implement Me!"; end
def level; raise "Implement Me!"; end

I'd define a method to easy this or use the approach shown below (stored
mandatory method names).

class Class
def method_stub(m)
class_eval { define_method(m) { raise "Implement Me!" } }
end
end

class Base
method_stub :foo
end

Base.new
RuntimeError: Implement Me!
from (irb):19:in `foo'
from (irb):19:in `foo'
from (irb):25
end
end
end

#Adaptor file
class Foo::Bar::DeviceType::Light::Dimmer::RadioRA
def name; 'Lutron RadioRA Wall Dimmer'; end
def manufacturer; 'Lutron'; end
def name; 'RA-ND6'; end
def turn_on; ...; end;
def turn_off; ...; end;
def level=(n); ...; end;
def level; ...; end;
Foo::Bar::DeviceType::add_device( self )
end

.. and the add_device method would
a) Create a dummy parent class and run through all the methods to see
if they throw errors.
b) Add the class into a list of instantiable adaptors if it works.

Questions
1) Is there a way to inspect the runtime state and figure out which
subclasses exist for a given class?

Here are two ways to ensure that you know your subclasses recursively:

class Base_1
def self.inherited(cl)
(@children ||= []) << cl
me = self
class <<cl;self;end.class_eval { define_method:)inherited) {|cl2|
me.inherited cl2} }
end

def self.subclasses() @children end
end

class Base
def self.inherited(cl)
(@children ||= []) << cl
def cl.inherited(cl2) superclass.inherited(cl2) end
end

def self.subclasses() @children end
end

class Sub1 < Base
end

class Sub2 < Sub1
end

class Sub3 < Sub2
end

p Base.subclasses

2) Is there a better way to force/detect if a subclass implements
certain methods?

You could store a set of method names in the base class and add a check
method for sub classes.

require 'set'

class Base
def self.inherited(cl)
(@children ||= []) << cl
def cl.inherited(cl2) superclass.inherited(cl2) end
end

def self.subclasses() @children end

def self.set_mandatory_methods(*m)
@mandatory = Set.new(m.flatten.map{|m| m.to_s})
end

def self.check_mandatory
@children.each do |cl|
diff = @mandatory - Set.new(cl.instance_methods)
raise "Methods missing: class=#{cl} methods=#{diff.to_a.inspect}"
unless diff.empty?
end
end
end

3) Is there a better way overall to achieve my goal?

I don't have a different approach right now. Sounds reasonable.

Kind regards

robert
 
G

Gavin Kistner

class Foo::Bar::DeviceType
def name; raise "Implement Me!"; end
def manufacturer; raise "Implement Me!"; end
def model; raise "Implement Me!"; end

class Light
def turn_on; raise "Implement Me!"; end
def turn_off; raise "Implement Me!"; end

class Dimmer
def level=(n); raise "Implement Me!"; end
def level; raise "Implement Me!"; end
end
end
end

#Adaptor file
class Foo::Bar::DeviceType::Light::Dimmer::RadioRA
def name; 'Lutron RadioRA Wall Dimmer'; end
def manufacturer; 'Lutron'; end
def name; 'RA-ND6'; end
def turn_on; ...; end;
def turn_off; ...; end;
def level=(n); ...; end;
def level; ...; end;
Foo::Bar::DeviceType::add_device( self )
end

Oops. I confused inheritance hierarchy with namespace hierarchy.
Sprinkle some "... < self" in there, along with a "... <
Foo::Bar::DeviceType::Light::Dimmer"
 
G

Gavin Kistner

I have a similar project which I outlined and began to code, but I ran
out of steam when I had problems debugging my X10 code.

See http://rubyhacker.com/domo.html

Let's see if we can develop some synergy or just plain merge these
projects.

Sweet. I'll probably run out of steam myself at some point, but so far
I'm optimistic. My project differs from yours significantly in the
answer to this question (for your site):

"So are you completely reinventing the wheel?"

Yes. Yes I am. :)

I realize this going into it, but (while I'll surely be hitting this
list again and again for bits of advice and help) I want to write this
on my own. (At least until it comes to the part of writing adaptors for
every bit of hardware out there.)

I hope you won't take any offense, Hal; it's nothing personal about you
or your project, but I want to be pig-headed and write all this from
scratch. For the fun, the ownership, and the experience.

Which is also why I'm writing my own 'plugin' system. I could probably
save some time and idiot-mistakes by investigating another
well-thought-out solution like FreeRIDE, but a) like so many fools, I
loathe doing prior-work research, and b) I hope that my solution will
be incredibly clean, particularly because it is perfectly-tailored to
my needs.


Thanks very much for the offer, and I may try and pick your brain later
and/or dump the whole pigheaded approach altogether, but for now I'm
walking the path on my own.


Now, if you could all just lift me up off the ground and point me in
the write direction, perhaps with a shove or two... ;)
 
H

Hal Fulton

Gavin said:
I hope you won't take any offense, Hal; it's nothing personal about you
or your project, but I want to be pig-headed and write all this from
scratch. For the fun, the ownership, and the experience.

I concur totally.

But the most important thing we can steal... is ideas. ;) And when you
steal ideas and implement them, you still get the thrill of invention.

If your project became usable and did the stuff I wanted, I would never
look at mine again. :)

So even if you don't like my architecture, please try to allow for the
possibility of all the functionality I've imagined.

In other words, I urge you not to construct a framework in which certain
things become impossible.

Good luck...


Hal
 
B

Brian Candler

I'm looking for advice on how to design code so that I can dynamically
add/remove instantiable classes, where each of these classes have
values for a set of properties defined by a parent class.

I think it will turn out to be much simpler than the examples you have
written; there is no need to make a class hierarchy as you would in C++. In
Ruby, the concept of "class" is nowhere near as important as in C++; it's
just a convenient factory for creating objects which happen to implement the
same set of methods, at least at the time when they were created. Each
object individually can have methods added and removed dynamically, so
what's important is whether a particular object X has method Y right now,
rather than the class it came from.

So what I suggest is that you just make objects, which respond to methods
which make sense to them, in any way which is simple and convenient.

class DumbLight
def initialize(location) ... end
def turn_on ... end
def turn_off ... end
end

class DimmerLight
def initialize(location) ... end
def turn_on ... end
def turn_off ... end
def level=(x) ... end
def level ... end
end

switches = []
switches << DumbLight.new('hallway')
switches << DumbLight.new('staircase')
switches << DimmerLight.new('lounge')

If you try to send a "level=" method to a DumbLight, then you will get a
NoMethodError exception, which you can catch easily. All it's saying is,
this object doesn't know how to do that.

If it saves you work, you can make class DimmerLight a subclass of
DumbLight; but you really don't have to if it doesn't make sense to do so,
e.g. if you don't re-use any significant amount of code.

Most importantly, you really don't have to have a common ancestor class
which contains methods that most or all your objects are expected to respond
to.
a) Create a dummy parent class and run through all the methods to see
if they throw errors.
b) Add the class into a list of instantiable adaptors if it works.

All classes are instantiable, and the methods they respond to may vary at
run-time (even after the object has been instantiated), so it usually
doesn't make sense to do this.

Just take a pool of objects representing the things you want to control and
present it to the user. If you want to have the GUI dynamically adjust
itself for each object, then you can check if the method exists for each
object before you present it:

gui.add_button("Turn On", foo, :turn_on) if foo.respond_to?:)turn_on)
gui.add_button("Turn Off", foo, :turn_off) if foo.respond_to?:)turn_off)

(or the objects themselves could contain code which knows how to create the
appropriate GUI controls, but personally I prefer to keep knowledge about
presentation in the GUI. After all, you could have many different types of
interface controlling the same set of objects: web, command-line, Tcl/Tk,
etc)

You can also use #arity to work out how many parameters a method takes, if
you want your GUI to present the right number of fields automatically:

class OnOffSwitch
def initialize(ipaddress, port)
@addr = ipaddress
@port = port
end
...
end

p OnOffSwitch.instance_method:)initialize).arity
=> 2

However, in this case, it may be better to return a data structure designed
for the GUI to prompt the information. This can include a human-readable
description, a validation regexp, and perhaps a method to call to convert it
from a string into the desired format.

class OnOffSwitch
def self.initialize_parameters
[
["IP Address", 15, /\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/, nil],
["Port", 5, /\A\d+\z/, :to_i],
]
end
end

p OnOffSwitch.initialize_parameters

Equally, you can ask what methods an object has, but it probably makes sense
to return an array of methods which the user can *sensibly* call on this
object.
Questions
1) Is there a way to inspect the runtime state and figure out which
subclasses exist for a given class?

Yes, there is:
ObjectSpace.each_object(Class) { |klass| p klass, klass.ancestors }
But usually that's not what you want to do.
2) Is there a better way to force/detect if a subclass implements
certain methods?

Yes, use #respond_to?:)foo) to check if an object implements method foo,
rather than worrying about what class it is in. Or just call it anyway, and
catch the exception if it fails.

If you have a common set of methods which are shared by many objects,
consider putting them into a Module which can then be included in a class:

module OnOff
def turn_on ... end
def turn_off ... end
end

class DumbLight
include OnOff
...
end

class DimmerLight
include OnOff
...
end

as that's more flexible than straightforward class inheritance. Also you can
add the methods to individual objects:

fishes = FishTank.new('hallway')
fishes.extend OnOff
# this is the only fishtank with an on/off switch

Finally, as a general point, do consider delegation rather than inheritance;
that is, object X "has_a" Y, rather than object X "is_a" Y. It often turns
out to be a far more flexible way to make objects with composite or more
'intelligent' behaviour than the base object.

class Fishtank
def initialize
@light = OnOffSwitch.new
@pump = OnOffSwitch.new
end
def turn_on
@light.turn_on
@pump.turn_on
end
def turn_off
@light.turn_off
@pump.turn_off
end
end
3) Is there a better way overall to achieve my goal?

I hope the above makes some sense :)

Regards,

Brian.
 
B

Brian Schroeder

[Really nice essay on ruby inheritance/mixin/extension snipped]

You should put this onto the web somewhere. Maybe in ruby-garden.org

Regards,

Brian
 
G

Gavin Kistner

You could store a set of method names in the base class and add a check
method for sub classes.

require 'set'

class Base
def self.inherited(cl)
(@children ||= []) << cl
def cl.inherited(cl2) superclass.inherited(cl2) end
end

Whoa.

a) I had no idea about the #inherited method. Awesome. (In my defense,
there appears to be a bug in the docs; in my ri, my own rdoc
compilation, and the current docs on ruby-doc.org, Class#inherited
appears in the method list, but links to #singleton_method_undefined.
e.g. http://www.ruby-doc.org/core/classes/Class.html#M000472

b) I didn't really realize that you could use instance variables on the
class itself; this had been what was holding up my method-tracking
implementation. thanks!

c) While that second line is tricky, I actually prefer the hierarchical
storage which occurs without it, since it allows for creating a
hierarchical, visual representation of the class hierarchy. (Or is
there a much cleaner/more built-in way than the following?)

class Foo
def self.inherited( k )
(@subclasses ||= []) << k
#def k.inherited(k2)
superclass.inherited(k2)
end
end

def self.show_subclasses( indent=0 )
puts "\t"*indent + self.inspect
@ subclasses.each{ |k|
k.show_subclasses( indent+1 )
} if @ subclasses.respond_to? :each
end

class Bar < self
class Jee < self; end
end
class Whee < self; end
end
class Foo2 < Foo; end

Foo.show_subclasses( )
#=> Foo
#=> Foo::Bar
#=> Foo::Bar::Jee
#=> Foo::Whee
#=> Foo2

diff = @mandatory - Set.new(cl.instance_methods)

Ooh, nice, I was going to just yell on a first-not-found basis but the
Set is a very elegant way of doing it.


Thank you very much for your help, Robert. It's exactly what I needed!
:)
 
D

David Ross

--- Gavin Kistner said:
a) I had no idea about the #inherited method.
Awesome. (In my defense,
there appears to be a bug in the docs; in my ri, my
own rdoc
compilation, and the current docs on ruby-doc.org,
Class#inherited
appears in the method list, but links to
#singleton_method_undefined.
e.g.
http://www.ruby-doc.org/core/classes/Class.html#M000472

What? You mean you haven't read ruby source code? Its
been around since 1.8.0 release. Every good programmer
should read sources.

--dross




__________________________________
Do you Yahoo!?
New and Improved Yahoo! Mail - 100MB free storage!
http://promotions.yahoo.com/new_mail
 
G

Gavin Kistner

Each object individually can have methods added and removed
dynamically, so
what's important is whether a particular object X has method Y right
now,
rather than the class it came from.

I agree with a lot of what you say in general, Brian. Although with the
above, I don't suspect that there will be any code extending specific
instances on the fly; it simply wouldn't occur in the Plan that's in my
head.

While I'm normally a huge duck-typing-plus fan, I'm waffling in this
case. I think the reason is that I hope to have adaptor modules
(classes) being contributed from many different authors, providing me
with two challenges:

1) Given the large number of devices which fit under 'automation', I
want a hierarchical organization of devices which may be available. If
I leave it up to each author, I fear the user will install an adaptor
for their hardware and find that it's under LightSwitch/Dimmer while
every other similar adaptor is in LightSwitches/Adaptors. Or worse,
it's in DimmerSwitches, or Electrical/Wall/Analog/Slider.

So, I feel the need to impose my own template hierarchy (at least as a
starting framework) for displaying the adaptors. While I could use a
hierarchical storage of symbols, I like the idea of using a class
namespace hierarchy. It gives me hierarchical classification of an
adaptor, and at the same time gives me a modicum of protection from
adaptor authors running over each others' code (through namespace
collisions).

2) Similar to the above, the more leeway I give the authors in the
method naming convention, the more I fear that some switches will have
"Turn On", some will have "On", and some smartass authors will use
"Illuminate".Having thought about your points, I will certainly need to
look at the exposed methods for any adaptor and be able to use them.
(Who knows what bizarre functionality I will forget to include or
account for?) But, for the sake of consistency, I think I want to say
"Damnit, if you have a 'Light' class, you better have a :turn_on
method."

And again, these methods should be hierarchically organized,
conveniently corresponding to the device hierarchy already being
defined to solve the classification and namespace problem.

Just take a pool of objects representing the things you want to
control and
present it to the user. If you want to have the GUI dynamically adjust
itself for each object, then you can check if the method exists for
each
object before you present it:

gui.add_button("Turn On", foo, :turn_on) if
foo.respond_to?:)turn_on)
gui.add_button("Turn Off", foo, :turn_off) if
foo.respond_to?:)turn_off)

(or the objects themselves could contain code which knows how to
create the
appropriate GUI controls, but personally I prefer to keep knowledge
about
presentation in the GUI. After all, you could have many different
types of
interface controlling the same set of objects: web, command-line,
Tcl/Tk,
etc)

Just to clarify my earlier point - you're right, the adaptors shouldn't
know how to make the GUI, but the GUI does need to know how to describe
the interface. What you list above is likely equivalent to what I'll
end up doing (ensuring that the method exists). However, since I can't
know all the methods/labels which might be needed, I'll likely be
ensuring that it exists by iterating the exposed methods, and at that
point I'm at the naming mercy of the author. And I don't want to be at
their mercy for certain core functionality.


However, in this case, it may be better to return a data structure
designed
for the GUI to prompt the information. This can include a
human-readable
description, a validation regexp, and perhaps a method to call to
convert it
from a string into the desired format.

Aye, I'm thinking even further (for rich GUI), along the lines of:

class Foo::Bar::Whee::La < Foo::Bar::Whee
def turn_on; ...; end
def get_funky( speed, dance_style, song ); ...; end
...
describe_method( :turn_on, "Turn On", "Turns on the switch" )
describe_method( :get_funky,
"Get Funky",
"Breaks down to the beat.",
{
:speed => {
:type => :integer,
:min => 0,
:max =>100
},
:dance_style => {
:type => :list,
:values => ['Hip Hop','Disco' ]
},
:song => {
:type => :file,
:mask => /\.(mp3|ogg|aiff)$/
}
}
)
end

If you have a common set of methods which are shared by many objects,
consider putting them into a Module which can then be included in a
class:

Well, as Robert correctly surmised, I don't actually want to implement
common code to them. (Some adaptor authors may want to, and they're
welcome to define their own modules if they wish). What I'm probably
going to end up with is something like:

class Device
def self.mandatory_methods= ( *method_names )
@mandatory = method_names.flatten.to_set
@mandatory.merge( superclass.mandatory_methods ) if
superclass.respond_to? :mandatory_methods
end

def self.mandatory_methods
@mandatory
end

self.mandatory_methods = [:name, :manufacturer, :models]

class Light < self
self.mandatory_methods = [:turn_on, :turn_off]
class Dimmer < self
self.mandatory_methods = [:level=, :level]
end
end

class Outlet < self
self.mandatory_methods = [:turn_on, :turn_off]
end
end

along with a test for these methods as part of my #instance handling.

Finally, as a general point, do consider delegation rather than
inheritance;
that is, object X "has_a" Y, rather than object X "is_a" Y. It often
turns
out to be a far more flexible way to make objects with composite or
more
'intelligent' behaviour than the base object.

Good advice; I'll certainly think about some nice way to group
primitives in the user UI.


Thanks so much for your considered advice.
 
G

Gavin Kistner

class Foo::Bar::Whee::La < Foo::Bar::Whee
def turn_on; ...; end
def get_funky( speed, dance_style, song ); ...; end
...
describe_method( :turn_on, "Turn On", "Turns on the switch" )
describe_method( :get_funky,
"Get Funky",
"Breaks down to the beat.",
{
:speed => {
:type => :integer,
:min => 0,
:max =>100
},
:dance_style => {
:type => :list,
:values => ['Hip Hop','Disco' ]
},
:song => {
:type => :file,
:mask => /\.(mp3|ogg|aiff)$/
}
}
)
end


Oops, I forgot that Hashes in Ruby aren't ordered. But you get the
idea, albeit with an array and an extra hash spot naming the parameter.

Or maybe by the time I'm done, Ruby will have added keyword-arguments
natively supported ;)
 
B

Brian Candler

While I'm normally a huge duck-typing-plus fan, I'm waffling in this
case. I think the reason is that I hope to have adaptor modules
(classes) being contributed from many different authors, providing me
with two challenges:

1) Given the large number of devices which fit under 'automation', I
want a hierarchical organization of devices which may be available. If
I leave it up to each author, I fear the user will install an adaptor
for their hardware and find that it's under LightSwitch/Dimmer while
every other similar adaptor is in LightSwitches/Adaptors. Or worse,
it's in DimmerSwitches, or Electrical/Wall/Analog/Slider.

If I understand rightly, I think the main constraint here is that you want
to organise the classes logically for the user to see when selecting one (to
create a new instance of a DimmerSwitch, for example, selecting it from a
tree rather than a long linear scrolling list).

You can always have a separate hierarchy for display purposes - or even
multiple different hierarchies, or the same class popping up in multiple
places in the same displayed hierarchy. It doesn't have to follow a class or
module hierarchy, and although it could, I think it will limit your
flexibility if you do.

You might, for example, want to give one view organised by manufacturer, and
another organised by device type.
and at the same time gives me a modicum of protection from
adaptor authors running over each others' code (through namespace
collisions).

If that's a concern, then modules should be organised by vendor/author, not
by function, so that each author is responsible for her own namespace.

module ACMEwidgets
class OnOffSwitch
...
end
end

That's another reason for making the organisation of objects for display
purposes separate from the module/class hierarchy (unless you're happy to
organise the display by vendor).

Of course, when you do "require 'acmewidgets'", you might want the classes
to appear in the correct places in the GUI hierarchy automagically. To do
that, I think they should export an "index card", rather like when a new
book arrives at the library - the librarian looks at the index card to work
out where to file it.

To support that, you need to define your own classification system, and then
modules can pick the one which suits them best.

require 'homecontrol'
module ACMEwidgets
class OnOffSwitch
def self.indexcard
[Homecontrol::DevType::Switch, "ACME Widgets model 345X mains controller"]
end
end
end

Or you could use a constant with a well-known name within the class (the
details of the exporting mechanism are unimportant, IMO)
2) Similar to the above, the more leeway I give the authors in the
method naming convention, the more I fear that some switches will have
"Turn On", some will have "On", and some smartass authors will use
"Illuminate".Having thought about your points, I will certainly need to
look at the exposed methods for any adaptor and be able to use them.
(Who knows what bizarre functionality I will forget to include or
account for?) But, for the sake of consistency, I think I want to say
"Damnit, if you have a 'Light' class, you better have a :turn_on
method."

Then you're imposing a duck-typing system anyway. You need to provide
hard-written guidelines for module authors, for each sort of device, what
methods it should have.

Although really, I think that end users are not likely to be invoking
methods on their devices directly (unless they are Hal), in which case the
GUI can take care of it.
Well, as Robert correctly surmised, I don't actually want to implement
common code to them. (Some adaptor authors may want to, and they're
welcome to define their own modules if they wish). What I'm probably
going to end up with is something like:

class Device
def self.mandatory_methods= ( *method_names )
@mandatory = method_names.flatten.to_set
@mandatory.merge( superclass.mandatory_methods ) if
superclass.respond_to? :mandatory_methods
end

def self.mandatory_methods
@mandatory
end

self.mandatory_methods = [:name, :manufacturer, :models]

class Light < self
self.mandatory_methods = [:turn_on, :turn_off]
class Dimmer < self
self.mandatory_methods = [:level=, :level]
end
end

class Outlet < self
self.mandatory_methods = [:turn_on, :turn_off]
end
end

along with a test for these methods as part of my #instance handling.

Why, then, have these arrays of mandatory_methods? What exactly are you
going to do if the class turns out not to have a method that you claim is
mandatory? I guess you could write code which automatically mails the
offending module back to the author and rm's it from the filesystem! :)

Seriously... I am a believer in "do the simplest thing which can possibly
work". If the method doesn't exist - then when you call it, Ruby will raise
an exception for you. There's no need to pre-list the methods expected, nor
to do
def turn_on
raise "Not implemented!"
end
in a superclass, because that's essentially the default behaviour which Ruby
has.

As another example: you were talking about using introspection to work out
which classes were subclasses of a top class, to identify which controllable
objects exist in the system. Sure you can do that - but it might be simpler
just to set a global variable which contains an array or hash of them (a
hash indexed by name for the GUI to display). After all, how often do you
add a new *class* of object to the system, like a new type of light switch
(as opposed to a new instance of a light switch)? You'll probably edit a .rb
file to add the appropriate "require" line anyway, so adding a new entry
into a global list of usable object classes isn't hard.

I suppose the introspection way is a little prettier:

module Homecontrol
module Controllable # dummy, just flags that this object is controllable
end
end

...

module ACMEwidgets
class OnOffSwitch
include Homecontrol::Controllable
DESCRIPTION = "ACME Widgets model 345X mains controller"
end
end

...

klasses = []
ObjectSpace.each_object(Class) { |k| klasses << k if k.include? Homecontrol::Controllable }
klasses.each { |k| puts k.const_get('DESCRIPTION') }

But I'd think of that as cake-icing really.

Cheers,

Brian.
 
E

Eivind Eklund

(Referring to design of a home automation system, using hierarchies to manage )

I don't have time to go through point by point (as what you've written
is fairly verbose), so I'll try to to address what I see as the main
points. Sorry for the somewhat abrupt tone; I don't have time to
write "nicely", but the advice is well-meant :) Feel free to ask for
more details.

The design as it is seems to conceptually mix
* Namespacing the modules of external authors (good idea)
* Defining interfaces they can plug into (good idea at a point)
* Using a hierarchy for these interfaces (most likely a good idea)
* Forcing every module to plug into a single point in a single
hierarchy (bad idea).

I'd separate these, and redesign a la this:

== Provide a namespace that authors can put their plug-in modules in. ==

I'd grab some area of the Ruby namespace (e.g,
HomeAutomation::Device), and then tell each author to use their e-mail
address "turned on its head" as a subnamespace they can manage
themselves. This would give any devices I wrote the module
HomeAutomation::Device::Org::FreeBSD::Eivind::<something>. It's long,
but it is unique.

== Define interfaces the can plug into ==

I'd grab a namespace for this (e.g. HomeAutomation::Interface, too,
and provide a bunch of plug in points/interfaces. Every device under
HomeAutomation::Device would export a list of what interfaces it
supported. Examples of interfaces would be ToggleSwitch or
VoltageRegulator; a device could be one of them (a pure toggle, or a
voltage regulator that had a minimum higher than 0), or both.

== Device finding ==

I'd just iterate over the namespace in HomeAutomation::Device to find
anything that's there.


I'd also start off with bringing anything written by external authors
"Into the fold" - import it into my own version control repository,
and give them commit privileges. That way, it is possible to refactor
the code together with the rest of the system. I'd also build the
system with heavy testing from the start, to enable that refactoring.

Remember: The standard advice is that you can't build a framework
until you've done (and deployed) three full systems. If you're going
to be more ambitious than that, at least cover all the bases you can.

Eivind.
 
R

Robert Klemme

Gavin Kistner said:
You could store a set of method names in the base class and add a check
method for sub classes.

require 'set'

class Base
def self.inherited(cl)
(@children ||= []) << cl
def cl.inherited(cl2) superclass.inherited(cl2) end
end

Whoa.

:)

c) While that second line is tricky, I actually prefer the hierarchical
storage which occurs without it, since it allows for creating a
hierarchical, visual representation of the class hierarchy. (Or is there a
much cleaner/more built-in way than the following?)

class Foo
def self.inherited( k )
(@subclasses ||= []) << k
#def k.inherited(k2)
superclass.inherited(k2)
end
end

def self.show_subclasses( indent=0 )
puts "\t"*indent + self.inspect
@ subclasses.each{ |k|
k.show_subclasses( indent+1 )
} if @ subclasses.respond_to? :each
end

You can do "if @subclasses" here because nil ~ false.
class Bar < self
class Jee < self; end
end
class Whee < self; end
end
class Foo2 < Foo; end

Foo.show_subclasses( )
#=> Foo
#=> Foo::Bar
#=> Foo::Bar::Jee
#=> Foo::Whee
#=> Foo2



Ooh, nice, I was going to just yell on a first-not-found basis but the Set
is a very elegant way of doing it.


Thank you very much for your help, Robert. It's exactly what I needed! :)

You're welcome! I'm glad I could help.

robert
 
M

Markus

INTRODUCTION

As some of you no doubt know by this point, I am in the process of
validating a medium large (~14,000 line) application on 1.8.1 & 1.8.2
(this is the same one I was working on last spring, when I was brining
it from 1.6.8 & other three other languages into pure ruby 1.8.0).

I have found what appears to be a bug. Prior to digging in to the
ruby source, I'm wondering if anyone else is working this, and/or if
anyone has any thoughts on it.

SAMPLE CODE

The following example program exhibits the bug. A routine which
takes one or more arguments, any of which may be of any type, is
implemented as both a procedure and a Proc object. The semantics should
be consistent and equivalent, regardless of the version used or the
class or number of the actual parameters.

def test(head,*rest)
print "head = #{head.inspect} "
print "rest = #{rest.inspect}\n"
end

test2 = Proc.new { |head,*rest|
print "head = #{head.inspect} "
print "rest = #{rest.inspect}\n"
}

test:)a,:b,:c)
test([:a],:b,:c)
test([:a,:b],:c)
test([:a,:b,:c])
print "-----------------\n"
test2.call:)a,:b,:c)
test2.call([:a],:b,:c)
test2.call([:a,:b],:c)
test2.call([:a,:b,:c])


EXPECTED RESULTS (actual, on 1.8.0)

Under 1.8.0, we get exactly what we would expect under all tested
conditions--note that it always correctly distinguishes the first
argument from the rest, and produces the same result regardless of which
form is used:

head = :a rest = [:b, :c]
head = [:a] rest = [:b, :c]
head = [:a, :b] rest = [:c]
head = [:a, :b, :c] rest = []
-----------------
head = :a rest = [:b, :c]
head = [:a] rest = [:b, :c]
head = [:a, :b] rest = [:c]
head = [:a, :b, :c] rest = []

ACTUAL RESULTS (on 1.8.1 & 1.8.2)

But under 1.8.1 & 1.8.2 the Proc object produces aberrant results
in the special case of a single array parameter, acting as if the actual
parameter had been prefixed with an "*" when in fact it was not:

head = :a rest = [:b, :c]
head = [:a] rest = [:b, :c]
head = [:a, :b] rest = [:c]
head = [:a, :b, :c] rest = []
-----------------
head = :a rest = [:b, :c]
head = [:a] rest = [:b, :c]
head = [:a, :b] rest = [:c]
head = :a rest = [:b, :c]
^^^^^^^^^^^^^^^^^ incorrect


Thoughts? In the absence of helpful pointers I will try to trace
it in the code myself, but I don't want to waste a lot of time if this
is a known issue being worked by someone else (but I'm, as always,
willing to pitch in and help if needed).

-- Markus
 
M

Markus

M> test2 = Proc.new { |head,*rest|

try to replace `Proc.new' with `proc'

That still works, but it doesn't really solve the problem with Proc
(or, more to the point in my code, sub-classes of it). In fact, it
raises my eyebrows further. When I type:

x.call(...)

how the complier interprets "..." shouldn't depend on the history of how
object x was created.

-- Markus
 
M

Markus

That still works, but it doesn't really solve the problem with Proc
(or, more to the point in my code, sub-classes of it). In fact, it
raises my eyebrows further. When I type:

x.call(...)

how the complier interprets "..." shouldn't depend on the history of how
object x was created.

User defined analogs to proc() also fail in the same way:

def my_proc(&b); b; end

x = my_proc { |head,*rest| ... }

interpolates an erroneous "*" when (and only when) called with a single
array.

If (as appears to be the case) no one else is working on thus I'll start
trying to tack it down.

-- MarkusQ
 
M

Markus

Well, that didn't take long. Although I sadly can not read
Japanese, from this:

Thu Oct 30 02:46:35 2003 Yukihiro Matsumoto <[email protected]>

* eval.c (proc_invoke): single array value to normal Proc#call
(i.e. not via lambda call), should be treated just like yield.
[ruby-dev:21726]

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/21726

...it appears that yield was broken and so it was decided to break
Proc#call to match it, rather than fixing yield. It appears that that
was done by adding the following:

from eval.c near line 7175:

pcall = data->flags & BLOCK_LAMBDA ? YIELD_PROC_CALL : 0;
if (!pcall && RARRAY(args)->len == 1) {
avalue = Qfalse;
args = RARRAY(args)->ptr[0];
}

(NOTE: I do not claim to be C programmer, and this is just is just a
preliminary exploration).

I will admit, having just spent six months full time getting 14,000
lines of code in ruby 1.8.0 to validate against a legacy code test
suite, including several out of state trips for multi-day meeting to
justify the validation process, etc. I a tad worried that what I had
thought would be a week or so of re-running the tests on 1.8.1 & 1.8.2
is looking like something that will be much, much worse.

Ok, not a tad worried. Near panic maybe?

I can of course back the change out (hurray for open source!) but
that would mean a fork forever more, or having my successors repatch
each new version for the rest of time--neither of which I care for. So:

* Is there anyone who reads Japanese or who is aware of any
discussion of this point in english that can point out to me if
I am looking at things incorrectly?
* Is it too late to argue against this change?
* It goes against POLS, since 1) adding an argument to an
argument list can _reduce_ the number of arguments seen
by the Proc, 2) adding or removing one argument can
change the class of another argument, 3) it makes it
impossible (so far as I can see) to write iterators that
process nested arrays in a reasonable fashion.
* It breaks a design pattern (head,*rest) with a forty
year history which is used in a great quantity of
published literature, including the heritage of lisp,
prolog, smalltalk, etc.
* If changed back, anyone wanting the "new" behavior could
get it by simply placing an "*" before the parameter
passed in
* If NOT changed, it is not at all clear how the behavior
could be cleanly worked around
* If I am too late to this debate, is there anyway that this could
be made conditional, e.g. by putting a flag on the Proc, or
adding a global setting or...? I would want something that was
within the ruby code, rather than something that affected the
compilation of ruby (e.g. a compilation/configuration option)
because having multiple versions of ruby floating around with
different semantics is dangerous.

I am willing to do the work on this, but I will need someone to vet
my C, and could use some guidance as to the best route to proceed.


-- Markus
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top