Complex Library Object/Class and its Interface

Discussion in 'Ruby' started by Trans, Jan 24, 2005.

  1. Trans

    Trans Guest

    Been beating my head against this one for far too long now, and I think
    others might be interested in it. I have a lib where the user needs to
    define a set of "things" that have both assignable data and assignable
    functions. Also they should be extensible so the user can add
    additional functionality. It's a bit difficult to explain so I will
    just give my exmaple.

    |class Parser::Token
    |
    | class << self
    | def esc(str) ; Regexp.escape(str) ; end
    | def unit? ; @unit ; end
    | def unit(u) ; @unit = ( u ? true : false ) ; end
    | def exclusive? ; @exclusive ; end
    | def exclusive(excl) ; @exclusive = ( excl ? true : false ) ; end
    | def priority ; @priority ||= 10 ; end
    | def priority(pri) ; @priority = pri ; end
    | def <=> ; @priority ; end
    | def start_exp(match=nil) ; @start.call(match) ; end
    | def start(&blk) ; @start = blk ; end
    | def stop_exp(match=nil) ; @stop.call(match) ; end
    | def stop(&blk) ; @stop = blk ; end
    | end
    |
    | attr_reader :parent, :match, :body
    |
    | def initialize( parent, match )
    | @parent = parent
    | @match = match
    | @body = []
    | end
    |
    | def <<( content ) ; @body << content ; end
    | def last ; @body.empty? ? @body : @body.last ; end
    | def empty? ; @body.empty? ; end
    | def pop ; @body.pop ; end
    | def each(&blk) ; @body.each(&blk) ; end
    |
    |end #class Parser::Token

    So then the lib usr can define each of their "things" based on this,
    minimally:

    |class Marker < Parser::Token
    |
    | exclusive false
    | start { %r{ \< (.*?) \> }mx }
    | stop { |match| %r{ \< [ ]* (#{esc(match[1])}) (.*?) \. \> }mx }
    |
    | #... user's optional methods ...

    It toke me some time to work this out in itself. And I thought I had
    finally gotten a fairly nice interface here. But to my dismay, I just
    discovered that I can't subclass Marker b/c I loose the definitions of
    the above attributes (ie. exclusive, start, stop). So now I'm back to
    rethinking the whole setup. (Also note that if the user's methods
    redefine #initialize or the other Token methods, it might cause the
    parser that uses it to break --another slight down side and a possible
    use case for RCR #198)

    So how does one properly build something like this in a nice neat way?
    Thanks,
    T.
     
    Trans, Jan 24, 2005
    #1
    1. Advertisements

  2. Hi,
    This is the first thought that entered my mind: turn the configuration
    methods into instance methods, move the method calls to #initialize,
    and call super there:

    class Marker < Parser::Token
    def initialize
    super
    exclusive false
    start { %r{ \< (.*?) \> }mx }
    stop { |match| %r{ \< [ ]* (#{esc(match[1])}) (.*?) \. \> }mx }
    end
    end

    class ExclusiveMarker < Marker
    def initialize
    super
    exclusive true
    end
    end


    Maybe that would work?
     
    Ilmari Heikkinen, Jan 24, 2005
    #2
    1. Advertisements

  3. Aahhh and 10 seconds after I sent that, another way surfaced:

    Parser::Token configurators could use define_method instead of
    @class_ivar, that way they'd be inherited.

    Mmh. I'm not really happy with either of my suggestions. Oh well, may
    others have better ways.


     
    Ilmari Heikkinen, Jan 24, 2005
    #3
  4. Trans

    Trans Guest

    Thanks Ilmari Heikkinen,

    A fair suggestion. The problem though is I'd have to make an instance
    of the class just to read the exclusive, start and stop information. I
    thought about doing it that way and creating dummy tokens to use, but
    using dummy tokens seemed rather unRubyish. It would be better if I
    could get that info with out instantiating the class.

    T.
     
    Trans, Jan 24, 2005
    #4
  5. Trans

    Aredridel Guest

    So how does one properly build something like this in a nice neat way?

    Sounds like you probably want to make those methods be macros,
    building instance methods that do what you want, rather than making
    them class singleton methods.
     
    Aredridel, Jan 24, 2005
    #5
  6. Trans

    Trans Guest

    Aredridel,

    Would that solve the access problem (i.e. being getting that info from
    the class). I just tried:

    | class Parser::Token
    |
    | class << self
    |
    | def inherited(klass)
    | klass.unit self.unit?
    | klass.exclusive self.exclusive?
    | klass.start &self.start
    | klass.stop &self.stop
    | end
    |
    | #...

    That solve the inheritence problem, it look like. But I'm jave
    troubles. I'm starting to think that the problem is simply that I'm
    trying to combine two different objects into one.

    T.
     
    Trans, Jan 24, 2005
    #6
  7. Trans

    Glenn Parker Guest

    What exactly do you mean when you say you "lose the definitions"? The
    definitions for Marker will remain accessible via the Marker scope, but
    a derived class will start out with its own set of (empty) definitions.
    Do you want auto-inherit for class instance variables?

    class Parent
    class << self
    def set_name(n)
    @name = n
    end
    def name
    (defined? @name) ? @name : superclass.name
    end
    end
    set_name "Smith"
    end

    class Child < Parent
    end

    class Grandchild < Child
    set_name "Jones"
    end

    puts "Parent.name = #{Parent.name}"
    puts "Child.name = #{Child.name}"
    puts "Grandchild.name = #{Grandchild.name}"

    Prints:

    Parent.name = Smith
    Child.name = Smith
    Grandchild.name = Jones
     
    Glenn Parker, Jan 24, 2005
    #7
  8. This inheritance will not be updated when you change the value in the
    parent class.

    This seems to come up often enough that maybe ruby needs a built in way
    to inherit mutable and overridable variables in the class hierarchy.
    Class vars, class instance vars, and constants don't quite do this.

    You can do it with superhash:

    require 'superhash'

    class Token
    class_superhash :stuff
    class << self
    def start(*args)
    if args.size > 0
    self.stuff[:start] = args[0]
    else
    self.stuff[:start]
    end
    end
    end
    end

    class Marker < Token
    start /marker start/
    end

    class FooMarker < Marker
    end

    p Marker.start
    p FooMarker.start

    __END__

    Output:

    /marker start/
    /marker start/
     
    Joel VanderWerf, Jan 24, 2005
    #8
  9. Trans

    Csaba Henk Guest

    Make those methods instance methods, but...

    Don't make it a class, make it a module. Extend it by itself.

    Instead of

    class Marker < Parser::Token
    ....

    do special customizations as

    module Marker
    extend self
    extend Parser::Token
    ....

    Csaba
     
    Csaba Henk, Jan 24, 2005
    #9
  10. Trans

    Trans Guest

    This seems to come up often enough that maybe ruby needs
    Does seem like that's something there should be a reasonbly straight
    foward way to do.
     
    Trans, Jan 25, 2005
    #10
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.