Design Advice: Sub-Class 'Instances'

Discussion in 'Ruby' started by Gavin Kistner, Sep 23, 2004.

  1. --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.
    --
    (-, /\ \/ / /\/
    --Apple-Mail-2--468029632--
    Gavin Kistner, Sep 23, 2004
    #1
    1. Advertising

  2. Gavin Kistner

    Hal Fulton Guest

    Gavin Kistner wrote:
    > 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
    Hal Fulton, Sep 23, 2004
    #2
    1. Advertising

  3. "Gavin Kistner" <> schrieb im Newsbeitrag
    news:...
    > 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

    >> Base.new.foo

    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
    Robert Klemme, Sep 23, 2004
    #3
  4. On Sep 23, 2004, at 8:46 AM, Gavin Kistner wrote:
    > 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"
    Gavin Kistner, Sep 23, 2004
    #4
  5. On Sep 23, 2004, at 9:19 AM, Hal Fulton wrote:
    > 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... ;)


    --
    (-, /\ \/ / /\/
    Gavin Kistner, Sep 23, 2004
    #5
  6. Gavin Kistner

    Hal Fulton Guest

    Gavin Kistner wrote:
    > 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
    Hal Fulton, Sep 23, 2004
    #6
  7. > 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.
    Brian Candler, Sep 23, 2004
    #7
  8. On Fri, 24 Sep 2004 01:07:43 +0900, Brian Candler wrote:

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


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

    Regards,

    Brian

    --
    Brian Schröder
    http://www.brian-schroeder.de/
    Brian Schroeder, Sep 23, 2004
    #8
  9. On Sep 23, 2004, at 9:29 AM, Robert Klemme wrote:
    > 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!
    :)
    --
    (-, /\ \/ / /\/
    Gavin Kistner, Sep 24, 2004
    #9
  10. Gavin Kistner

    David Ross Guest

    --- Gavin Kistner <> wrote:

    > 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
    David Ross, Sep 24, 2004
    #10
  11. On Sep 23, 2004, at 10:07 AM, Brian Candler wrote:
    > 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.

    --
    (-, /\ \/ / /\/
    Gavin Kistner, Sep 24, 2004
    #11
  12. On Sep 23, 2004, at 9:20 PM, Gavin Kistner wrote:
    > 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 ;)

    --
    "When I am working on a problem I never think about beauty. I only
    think about how to solve the problem. But when I have finished, if the
    solution is not beautiful, I know it is wrong."
    - R. Buckminster Fuller
    Gavin Kistner, Sep 24, 2004
    #12
  13. On Fri, Sep 24, 2004 at 12:20:54PM +0900, Gavin Kistner wrote:
    > 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.
    Brian Candler, Sep 24, 2004
    #13
  14. (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.
    Eivind Eklund, Sep 24, 2004
    #14
  15. "Gavin Kistner" <> schrieb im Newsbeitrag
    news:...
    > On Sep 23, 2004, at 9:29 AM, Robert Klemme wrote:
    >> 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.


    :)

    <snip/>

    > 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
    >
    >
    >> 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! :)


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

    robert
    Robert Klemme, Sep 24, 2004
    #15
  16. Gavin Kistner

    Markus Guest

    [PRELIMINARY] 1.8.1 & 2 proc bug

    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
    Markus, Sep 24, 2004
    #16
  17. Gavin Kistner

    ts Guest

    Re: [PRELIMINARY] 1.8.1 & 2 proc bug

    >>>>> "M" == Markus <> writes:

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

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


    Guy Decoux
    ts, Sep 24, 2004
    #17
  18. Gavin Kistner

    Markus Guest

    Re: [PRELIMINARY] 1.8.1 & 2 proc bug

    On Fri, 2004-09-24 at 10:18, ts wrote:
    > >>>>> "M" == Markus <> writes:

    >
    > 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
    Markus, Sep 24, 2004
    #18
  19. Gavin Kistner

    Markus Guest

    [BUG] 1.8.1 & 2 proc bug

    On Fri, 2004-09-24 at 10:28, Markus wrote:
    > On Fri, 2004-09-24 at 10:18, ts wrote:
    > > >>>>> "M" == Markus <> writes:

    > >
    > > 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.


    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
    Markus, Sep 24, 2004
    #19
  20. Gavin Kistner

    Markus Guest

    Re: [BUG] 1.8.1 & 2 proc bug

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

    Thu Oct 30 02:46:35 2003 Yukihiro Matsumoto <>

    * 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
    Markus, Sep 24, 2004
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John Wohlbier
    Replies:
    2
    Views:
    366
    Josiah Carlson
    Feb 22, 2004
  2. Ben
    Replies:
    2
    Views:
    886
  3. Replies:
    8
    Views:
    463
    James Stroud
    Jan 29, 2009
  4. Lawrence D'Oliveiro

    Death To Sub-Sub-Sub-Directories!

    Lawrence D'Oliveiro, May 5, 2011, in forum: Java
    Replies:
    92
    Views:
    2,024
    Lawrence D'Oliveiro
    May 20, 2011
  5. Nick Bo
    Replies:
    5
    Views:
    166
    Tim Hunter
    Sep 29, 2008
Loading...

Share This Page