Managing metadata about attribute types

Ask a Question related to Ruby, Design and Development.

  1. #1

    Default Managing metadata about attribute types

    Hi,

    I'm porting the Apache Jakarta Commons Digester (written in Java) to
    Ruby at the moment. This module processes xml in a rules-based manner.
    It is particularly useful for handling complex xml configuration files.

    However some of the very nice features of this module depend upon being
    able to introspect a class to find what attributes it has, and what
    their datatypes are.

    Finding attributes on a Ruby class is simple (just look for "attr="
    methods). Unfortunately, determining what object types it is valid to
    assign to that attribute is not so simple...

    I was wondering if there were any other Ruby projects which have faced
    this problem and come up with solutions? I would rather steal a solution
    than invent one :-)

    Example of problem:

    Input xml is:
    <stock>
    <stock-item name="spanner" cost="12.50"/>
    <stock-item name="screwdriver" cost="3.80"/>
    </stock>

    // java
    class StockItem {
    public void setName(String name) {....}
    public void setCost(float cost) {....}
    }

    # Ruby
    class StockItem
    attr_accessor :name
    attr_accessor :cost
    end

    In the java version, when the "cost" attribute is encountered in the xml
    input, it is seen that the target class has a setCost(float) method, so
    the string "12.50" is converted to a float before invoking the setCost
    method.

    I want to achieve the same effect in the Ruby version. I do *not* want
    to effectively invoke this in ruby:
    stock_item.cost=('12.50') # string passed


    Anyone have any references to "pre-existing art"???

    Thanks,

    Simon


    Simon Kitching Guest

  2. Similar Questions and Discussions

    1. Strong Typing (Managing metadata about attribute types)<Pine.LNX.4.44.0311171402340.1133-10
      > I don't advocate type checking much in either case. It is a performance the programming language euphoria has an interesting approach to this....
    2. Managing metadata about attribute types<Pine.LNX.4.44.0311082124290.32662-100000@ool-4355dfae.dyn.optonline.net>
      Hi -- On Sun, 9 Nov 2003, Ryan Pavlik wrote: The point is that objects which have methods defined in their singleton classes are no longer...
    3. Managing metadata about attribute types<Pine.LNX.4.44.0311082012580.32527-100000@ool-4355dfae.dyn.optonline.net>
      Hi -- On Sun, 9 Nov 2003, Ryan Pavlik wrote: You must not be a big fan of class methods :-) David
    4. Managing metadata about attribute types<Pine.LNX.4.44.0311080757320.9904-100000@ool-4355dfae.dyn.optonline.net>
      Hi -- On Sat, 8 Nov 2003, John W. Long wrote: There's no improvement in error messages that I can see with the StrongTyping module, and of...
    5. Managing metadata about attribute types<Pine.LNX.4.44.0311060707150.5008-100000@ool-4355dfae.dyn.optonline.net>
      Hi -- On Thu, 6 Nov 2003, Ryan Pavlik wrote: At this point you're waging a battle directly against the design of Ruby. Ruby allows you to...
  3. #2

    Default Re: Managing metadata about attribute types

    Simon Kitching wrote:
    > Hi,
    >
    > I'm porting the Apache Jakarta Commons Digester (written in Java) to
    > Ruby at the moment. This module processes xml in a rules-based manner.
    > It is particularly useful for handling complex xml configuration files.

    Are you familiar with the XMLDigester project listed in the RAA?

    [url]http://raa.ruby-lang.org/list.rhtml?name=xmldigester[/url]
    and
    [url]http://www.helenius.dk/ruby/digester/[/url]


    James Britt



    james@rubyxml.com Guest

  4. #3

    Default Re: Managing metadata about attribute types

    On Wed, 5 Nov 2003 09:38:16 +0900
    Simon Kitching <simon@ecnetwork.co.nz> wrote:
    > Hi,
    <snip>
    > I was wondering if there were any other Ruby projects which have faced
    > this problem and come up with solutions? I would rather steal a solution
    > than invent one :-)
    Yep. I'll take this opportunity to shamelessly plug some modules. I
    do this in Mephle. First, I use the StrongTyping module and write new
    attr_ functions, so I can do this:

    attr_accessor_typed String, :foo, :bar

    Now #foo= and #bar= complain if they don't get a String. Even better,
    I can use StrongTyping's type querying on foo= and bar= to get what
    they take, if I need to.

    Next, I use the MetaTags module for actually tagging what attributes
    exist:

    class_info <<-DOC
    !Class: Foo

    !attr foo: Foo
    !attr bar: Bar: This is an optional description of bar.
    DOC
    class Foo
    :
    end

    Now I can ask for information about the Foo class and look through the
    attributes that way.

    It works... I generate UIs from this information... and I'm working on
    some tools to eliminate redundancy and required typing.

    hth,

    --
    Ryan Pavlik <rpav@mephle.com>

    "Mmm! Power lines and paint chips! My childhood rocks!" - 8BT

    Ryan Pavlik Guest

  5. #4

    Default Re: Managing metadata about attribute types

    On Wed, 5 Nov 2003, Simon Kitching wrote:
    > Date: Wed, 5 Nov 2003 09:38:16 +0900
    > From: Simon Kitching <simon@ecnetwork.co.nz>
    > Newsgroups: comp.lang.ruby
    > Subject: Managing metadata about attribute types
    >
    > Hi,
    >
    > I'm porting the Apache Jakarta Commons Digester (written in Java) to
    > Ruby at the moment. This module processes xml in a rules-based manner.
    > It is particularly useful for handling complex xml configuration files.
    >
    > However some of the very nice features of this module depend upon being
    > able to introspect a class to find what attributes it has, and what
    > their datatypes are.
    >
    > Finding attributes on a Ruby class is simple (just look for "attr="
    > methods). Unfortunately, determining what object types it is valid to
    > assign to that attribute is not so simple...
    >
    > I was wondering if there were any other Ruby projects which have faced
    > this problem and come up with solutions? I would rather steal a solution
    > than invent one :-)
    >
    > Example of problem:
    >
    > Input xml is:
    > <stock>
    > <stock-item name="spanner" cost="12.50"/>
    > <stock-item name="screwdriver" cost="3.80"/>
    > </stock>
    >
    > // java
    > class StockItem {
    > public void setName(String name) {....}
    > public void setCost(float cost) {....}
    > }
    >
    > # Ruby
    > class StockItem
    > attr_accessor :name
    > attr_accessor :cost
    > end
    >
    > In the java version, when the "cost" attribute is encountered in the xml
    > input, it is seen that the target class has a setCost(float) method, so
    > the string "12.50" is converted to a float before invoking the setCost
    > method.
    >
    > I want to achieve the same effect in the Ruby version. I do *not* want
    > to effectively invoke this in ruby:
    > stock_item.cost=('12.50') # string passed
    >
    >
    > Anyone have any references to "pre-existing art"???

    i have a similar problem for parsing header informaion from satelite data, eg

    ....
    samples per scanline: 7322
    organization: band interleaved by scanline
    ....

    i want the first to be a Fixnum and the second to be a String. i know it's
    simple, but i've taken this approach:

    def samples_per_scanline= arg
    @samples_per_scanline =
    case arg
    when String
    raise ArgumentError.new(arg) unless arg =~ FLOAT_PAT
    arg.to_f
    when Numeric
    arg.to_f
    else
    raise TypeError.new(arg.class.to_s)
    end
    end

    this can be useful when you want to ensure that a @attr is of a certain type,
    but want to allow _different_ types in the call to set it...

    i'm not sure if modules like StrongTyping allow this. perhaps they do.


    i suppose one could automate this somehow to using a class_eval.

    ~/eg/ruby > cat float_attr.rb
    def Object.float_attr sym
    template = <<-'template'
    def %s= arg
    float_pat = %%r/^\s*[+-]?\d+(?:\.\d*)?\s*$/o
    case arg
    when String
    raise ArgumentError.new(arg.to_s) unless arg =~ float_pat
    @%s = arg.to_f
    when Numeric
    @%s = arg.to_f
    else
    raise TypeError.new(arg.class.to_s)
    end
    printf "%s = %%s\n", arg
    end
    def %s; @%s; end
    template
    code = template % [sym,sym,sym,sym,sym,sym]
    class_eval code
    end

    class C
    float_attr :foo
    end

    c = C.new
    c.foo = 42
    p c.foo


    ~/eg/ruby > ruby float_attr.rb
    foo = 42
    42.0


    anyhow - i see the desire for strong typing of attributes, but my personal
    opinion is that strong typing of method signatures is a bit anti ruby: better
    to munge inside the method than outside it lest we all tumble into c-- hell.

    __IMHO__

    -a
    --

    ATTN: please update your address books with address below!

    ================================================== =============================
    | EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
    | PHONE :: 303.497.6469
    | ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
    | STP :: [url]http://www.ngdc.noaa.gov/stp/[/url]
    | NGDC :: [url]http://www.ngdc.noaa.gov/[/url]
    | NESDIS :: [url]http://www.nesdis.noaa.gov/[/url]
    | NOAA :: [url]http://www.noaa.gov/[/url]
    | US DOC :: [url]http://www.commerce.gov/[/url]
    |
    | The difference between art and science is that science is what we
    | understand well enough to explain to a computer.
    | Art is everything else.
    | -- Donald Knuth, "Discover"
    |
    | /bin/sh -c 'for l in ruby perl;do $l -e "print \"\x3a\x2d\x29\x0a\"";done'
    ================================================== =============================


    Ara.T.Howard Guest

  6. #5

    Default Re: Managing metadata about attribute types

    On Wed, 5 Nov 2003, Simon Kitching wrote:

    # Hi,
    #
    # I'm porting the Apache Jakarta Commons Digester (written in Java) to
    # Ruby at the moment. This module processes xml in a rules-based manner.
    # It is particularly useful for handling complex xml configuration files.
    #
    # However some of the very nice features of this module depend upon being
    # able to introspect a class to find what attributes it has, and what
    # their datatypes are.
    #
    # Finding attributes on a Ruby class is simple (just look for "attr="
    # methods). Unfortunately, determining what object types it is valid to
    # assign to that attribute is not so simple...
    #
    I'm not exactly answering your question here, but I'd like to offer some
    words of caution...

    Be careful about relying on what "type" of object you've got (I'll
    avoiding going into detailed discussion of the fact that "type" and
    "class" are not equivalent). An object's class doesn't guarantee much of
    anything in Ruby. Javaisms may not apply:

    class String
    undef :upcase, :downcase, :chomp, :chop, :capitalize
    end

    naive_method("is this really a string?")

    or even better:

    class A
    end
    a = A.new
    def a.is_a?(x)
    true
    end

    As you can see, any code that that relies on this kind of "type checking"
    in Ruby is naive. Worse than that, the desire to shoe-horn
    Ruby into Java-like "strictness" can blind the user into missing the
    point, and therefore the full benefit, of what Ruby has to offer.

    As I said, I haven't actually addressed your problem here. If it were me,
    I would be looking for (or writing) something that generates code based on
    an XML Schema. What you're really looking for is convenience (as opposed
    to "type safety"). You want to be able to add 1 + 1 and not end up with 11.
    You could easily accomplish this via some tedious coding or you could
    generate the tedious code. By tedious, I mean things like this:

    class Person
    def age=(how_old)
    @age = Integer(how_old)
    end
    end

    Ruby being as dynamic as it is, it would be pretty easy to dream up a
    scheme to wrap existing classes with filters that could do this kind of
    conversion for you. And, of course, you could generate the filters.


    And now, the cold medication sets in....


    Good night,
    Chad


    Chad Fowler Guest

  7. #6

    Default Re: Managing metadata about attribute types

    On Wed, 5 Nov 2003 09:38:16 +0900, Simon Kitching wrote:
    > I'm porting the Apache Jakarta Commons Digester (written in Java)
    > to Ruby at the moment. This module processes xml in a rules-based
    > manner. It is particularly useful for handling complex xml
    > configuration files.
    You may want to note xml-configfile in addition to XMLDigester that
    James Britt mentioned.
    [url]http://raa.ruby-lang.org/list.rhtml?name=xml-configfile[/url]
    > Finding attributes on a Ruby class is simple (just look for
    > "attr=" methods). Unfortunately, determining what object types it
    > is valid to assign to that attribute is not so simple...
    Aside from Ryan Pavlik's StrongTyping module, I'm not sure that this
    is absolutely necessary. See below for a bit more information.
    > I was wondering if there were any other Ruby projects which have
    > faced this problem and come up with solutions? I would rather
    > steal a solution than invent one :-)
    I frankly don't see a reason to worry about this.
    > Example of problem:
    >
    > Input xml is:
    > <stock>
    > <stock-item name="spanner" cost="12.50"/>
    > <stock-item name="screwdriver" cost="3.80"/>
    > </stock>
    >
    > // java
    > class StockItem {
    > public void setName(String name) {....}
    > public void setCost(float cost) {....}
    > }
    > # Ruby
    > class StockItem
    > attr_accessor :name
    > attr_accessor :cost
    > end

    Why not do:

    class StockItem
    attr_accessor :name # Defaults to String
    attr_reader :cost # Returns cost
    def cost=(x)
    @cost = x.to_f
    end

    This way, you don't have to care what the appropriate type is --
    your type worries about it.

    -austin
    --
    austin ziegler * [email]austin@halostatue.ca[/email] * Toronto, ON, Canada
    software designer * pragmatic programmer * 2003.11.04
    * 22.46.52


    Austin Ziegler Guest

  8. #7

    Default Re: Managing metadata about attribute types

    On Wed, 2003-11-05 at 17:09, Austin Ziegler wrote:
    >
    > class StockItem
    > attr_accessor :name # Defaults to String
    > attr_reader :cost # Returns cost
    > def cost=(x)
    > @cost = x.to_f
    > end
    Hi Austin,

    Thanks for your reply.

    One of the goals of xmldigester is to be able to instantiate and
    initialise objects from some input xml without making any changes to the
    classes themselves.

    Thus if you already have a library of warehouse management classes, I
    can write some rules that can take an xml description of the contents of
    that warehouse and build appropriately configured objects without
    changing that library. And once the parsing is complete, the resulting
    tree of objects should look no different than one created using normal
    calls to the library API.

    In addition, the approach you suggest is quite labour-intensive;
    for every attribute, a "wrapper" method needs to be written.

    I feel Ryan's MetaTags approach is easier to use; a simple string format
    can be used to document the types to which strings from the xml input
    should be converted before assignment to various attributes.

    Regards,

    Simon


    Simon Kitching Guest

  9. #8

    Default Re: Managing metadata about attribute types

    On Wed, 2003-11-05 at 17:09, Chad Fowler wrote:
    > On Wed, 5 Nov 2003, Simon Kitching wrote:
    >
    > # Hi,
    > #
    > # I'm porting the Apache Jakarta Commons Digester (written in Java) to
    > # Ruby at the moment. This module processes xml in a rules-based manner.
    > # It is particularly useful for handling complex xml configuration files.
    > #
    > # However some of the very nice features of this module depend upon being
    > # able to introspect a class to find what attributes it has, and what
    > # their datatypes are.
    > #
    > # Finding attributes on a Ruby class is simple (just look for "attr="
    > # methods). Unfortunately, determining what object types it is valid to
    > # assign to that attribute is not so simple...
    > #
    > I'm not exactly answering your question here, but I'd like to offer some
    > words of caution...
    >
    > Be careful about relying on what "type" of object you've got (I'll
    > avoiding going into detailed discussion of the fact that "type" and
    > "class" are not equivalent). An object's class doesn't guarantee much of
    > anything in Ruby. Javaisms may not apply:
    Yes. I'm not strictly looking for "the type of the target attribute". As
    you state, the question "what type *is* the attribute" is something that
    is usually not worth asking in Ruby.

    What I'm really looking for is "what type should I convert the string
    extracted from the xml to before assignment to the attribute". Or, in
    other words I want to know the name of one type that can validly be
    assigned to the attribute, so I can generate an instance of that type
    from the input xml string value.

    This is slightly different from "what is the type of the attribute", but
    close enough that Ryan's MetaTags "type annotation" approach will
    probably work. It can be regarded as a way of giving "type hints". The
    StrictTyping module is not, I agree, what I want.

    In fact, the original Digester library has a similar problem when the
    declared type of an attribute is an abstract type. The Digester library
    essentially says "well, in that case you can't use the nice convenient
    SetPropertiesRule api. Use the more explicit (and long-winded)
    CallMethodRule api instead". Or you can create a BeanInfo class.
    Unfortunately Java apps face this "lack of target type info" only
    occasionally, while for Ruby it exists for every attribute....

    Whatever syntax is used to indicate "what type should the input be
    converted to" needs to be clean and concise because it will be used
    quite a lot to tell xmldigester about how to map xml to
    object-attributes.
    > As you can see, any code that that relies on this kind of "type checking"
    > in Ruby is naive. Worse than that, the desire to shoe-horn
    > Ruby into Java-like "strictness" can blind the user into missing the
    > point, and therefore the full benefit, of what Ruby has to offer.
    Yep. I can't see how to offer what I want without some type info,
    though.

    If you look at my original example, how to I know that
    item.cost = '3.50'
    is wrong (the instance will eventually trigger some error), and
    item.cost = '3.50'.to_f
    is the right thing to do?

    With Java it just happens magically and "does the right thing" (except
    in the abstract type case mentioned above). Surely Ruby can't be
    inferior :-).
    >
    > As I said, I haven't actually addressed your problem here. If it were me,
    > I would be looking for (or writing) something that generates code based on
    > an XML Schema.
    One of the xmldigester goals is *not* to require an XML Schema.

    There are "code generation" approaches that do this. In many
    circumstances they are a good solution

    However in many cases they are also a bad solution. The original
    Digester fills this ecological niche nicely in Java. I hope xmldigester
    will fill the same niche in Ruby. But that depends upon finding a
    reasonable solution to this type-of-attribute problem that leaves the
    library reasonably easy to use.

    And I'm not sure that the XML Schema approach works that well either.
    Yes, it can document the "type" of the data "in" the xml. But does that
    always match with the datatype you want to pass to an attribute? I'll
    think about this a bit, though. However if I do go down this road, I'll
    have to change the project name as it will be quite different from the
    Apache Digester ;-)
    > What you're really looking for is convenience (as opposed
    > to "type safety").
    Yep. Absolutely.
    > You want to be able to add 1 + 1 and not end up with 11.
    Or go
    item.cost = some_value
    and not have an exception thrown later when the class goes to use cost
    in some arithmetic operation.
    > You could easily accomplish this via some tedious coding or you could
    > generate the tedious code. By tedious, I mean things like this:
    >
    > class Person
    > def age=(how_old)
    > @age = Integer(how_old)
    > end
    > end
    Yep. Tedious indeed. And as mentioned in my reply to Austin, it is a
    goal to instantiate and initialise objects without requiring any changes
    to their code. Digester manages this fine.
    >
    > Ruby being as dynamic as it is, it would be pretty easy to dream up a
    > scheme to wrap existing classes with filters that could do this kind of
    > conversion for you. And, of course, you could generate the filters.
    The problem is : in order to automatically generate the filters, I need
    to know what type of object is required for each attribute. Full circle!
    That code in the Person class you show above could only be generated
    because a human "knew" that Integer was an appropriate type to store
    into the age attribute (and String was not). I just want to represent
    that info in the code somehow...
    > And now, the cold medication sets in....
    That may have been more information than I needed to know :-)
    >
    >
    > Good night,
    > Chad
    >
    Good afternoon...

    Simon



    Simon Kitching Guest

  10. #9

    Default Re: Managing metadata about attribute types

    Hi Ryan,

    Thanks for your reply.

    MetaTags ([url]http://raa.ruby-lang.org/list.rhtml?name=metatags[/url]) may be what
    I was looking for. I'll try to figure out exactly what it does over the
    next few days.

    Mephle looks very interesting .. might have to look into it later on.

    Regards,

    Simon
    > Next, I use the MetaTags module for actually tagging what attributes
    > exist:
    >
    > class_info <<-DOC
    > !Class: Foo
    >
    > !attr foo: Foo
    > !attr bar: Bar: This is an optional description of bar.
    > DOC
    > class Foo
    > :
    > end
    >
    > Now I can ask for information about the Foo class and look through the
    > attributes that way.
    >
    > It works... I generate UIs from this information... and I'm working on
    > some tools to eliminate redundancy and required typing.
    >
    > hth,

    Simon Kitching Guest

  11. #10

    Default Re: Managing metadata about attribute types

    This is a multipart message in MIME format

    --07557043-POCO-66642815
    Content-Type: text/plain; charset="iso-8859-1"
    Content-Transfer-Encoding: quoted-printable

    On Wed, 5 Nov 2003 13:27:05 +0900, Simon Kitching wrote:
    > On Wed, 2003-11-05 at 17:09, Austin Ziegler wrote:
    > One of the goals of xmldigester is to be able to instantiate and
    > initialise objects from some input xml without making any changes
    > to the classes themselves.
    > Thus if you already have a library of warehouse management
    > classes, I can write some rules that can take an xml description
    > of the contents of that warehouse and build appropriately
    > configured objects without changing that library. And once the
    > parsing is complete, the resulting tree of objects should look no
    > different than one created using normal calls to the library API.
    You're right, they shouldn't. But if your warehouse management
    classes don't do what they can to ensure their data integrity, then
    there's a problem with the classes -- not with the XML library. I'm
    not trying to be difficult here; just pointing out that I think
    you're trying to fix the problem from the wrong end.
    > In addition, the approach you suggest is quite labour-intensive;
    > for every attribute, a "wrapper" method needs to be written.
    Not really. See below for one option. The way that I've implemented
    this makes it easy to drop into place.
    > I feel Ryan's MetaTags approach is easier to use; a simple string
    > format can be used to document the types to which strings from the
    > xml input should be converted before assignment to various
    > attributes.
    Ryan's MetaTags really does not do anything different than I'm
    talking about except that it could can it (although I think
    StrongTyping does that better). See, you can make a simple
    metamethod that wraps this for you and it becomes a relatively
    simple change to make it "simple." If you don't want to create a
    method for each, define a proc and use the following extension to
    attr_accessor. (Does anyone else think that this is a good idea? I
    do. I'd love to see it become part of "standard" Ruby.)

    class Module
    alias_method :__attr_accessor, :attr_accessor

    def attr_accessor(block, *symbols)
    if block.kind_of?(Symbol)
    symbols.unshift(block)
    __attr_accessor(*symbols)
    else
    symbols.each do |get|
    var =3D "@#{get}"
    set =3D "#{get}=3D".intern
    self.class_eval do
    define_method(get) { self.instance_variable_get(var) }
    define_method(set) { |*val|
    self.instance_variable_set(var, block.call(*val))
    }
    end
    end
    end
    end
    end

    class Foo
    attr_accessor :item
    attr_accessor proc { |x| x.to_i }, :item_id
    attr_accessor proc { |x| x.to_f }, :cost
    end

    f =3D Foo.new
    f.item =3D "item"
    f.item_id =3D "37352"
    f.cost =3D "12.50"
    p f.inspect

    (I've actually attached a further enhanced and unit-tested version
    of this extension to this message. If anyone wants to add to the
    test cases, such as for array parameters, feel free.)

    On Wed, 5 Nov 2003 14:06:55 +0900, Simon Kitching wrote:
    >> As you can see, any code that that relies on this kind of "type
    >> checking" in Ruby is naive. Worse than that, the desire to
    >> shoe-horn Ruby into Java-like "strictness" can blind the user
    >> into missing the point, and therefore the full benefit, of what
    >> Ruby has to offer.
    > Yep. I can't see how to offer what I want without some type info,
    > though.
    >
    > If you look at my original example, how to I know that
    > item.cost =3D '3.50'
    > is wrong (the instance will eventually trigger some error), and
    > item.cost =3D '3.50'.to_f
    > is the right thing to do?
    >
    > With Java it just happens magically and "does the right thing"
    > (except in the abstract type case mentioned above). Surely Ruby
    > can't be inferior :-).
    It isn't inferior, and you don't need type info. Remember -- an
    object should validate or transform its own data. Using the method I
    described above, it becomes "cheap" to fix the problem as I see it
    without imposing a requirement for the use of type strictness that
    IMO, is a really bad idea for a Ruby library.
    > Yep. Tedious indeed. And as mentioned in my reply to Austin, it is a goal
    > to instantiate and initialise objects without requiring any changes to
    > their code. Digester manages this fine.
    Digest manages this because the objects in Java automatically take
    care of their own type. Sort of. The compiler prevents you from
    using any type except those that are signaled. If you have not
    massaged your own type attributes to make sure that they are
    receiving valid data, then there's something wrong with the classes
    themselves. NOT with Ruby for not providing type strictness.

    In several libraries I've written, I have either rejected types that
    I can't handle, or I have transformed types into what I can handle,
    or I have operated on the data and thrown an error because it can't
    be handled. I prefer being proactive, so I choose the former two
    methods most often.
    > The problem is : in order to automatically generate the filters, I need
    > to know what type of object is required for each attribute. Full circle!
    Again, not really. The class you're building has to know what it
    expects. It's an inverse of what you'd expect from a statically
    typed language, but I have found it easier to understand in the long
    run.
    > That code in the Person class you show above could only be generated
    > because a human "knew" that Integer was an appropriate type to store into
    > the age attribute (and String was not). I just want to represent that
    > info in the code somehow...
    But defining the Person class to convert its "age" parameter into an
    Integer does exactly that. Not as meta-data, to be sure, but in the
    only way that matters, IMO.

    -austin
    --
    austin ziegler * [email]austin@halostatue.ca[/email] * Toronto, ON, Canada
    software designer * pragmatic programmer * 2003.11.05
    * 02.12.59


    --07557043-POCO-66642815
    Content-Type: application/octet-stream; name="extend.rb"
    Content-Transfer-Encoding: Base64
    Content-Disposition: attachment; filename="extend.rb"

    Y2xhc3MgTW9kdWxlCiAgYWxpYXNfbWV0aG9kIDpfX2F0dHJfYW NjZXNzb3IsIDphdHRyX2FjY2Vz
    c29yCgogIGRlZiBfX2F0dHJfY29udmVydG9yKHN5bWJvbCwgY2 9udmVydG9yKQogICAgdmFyID0g
    IkAje3N5bWJvbH0iCiAgICBzZXQgPSAiI3tzeW1ib2x9PSIuaW 50ZXJuCiAgICBkZWZpbmVfbWV0
    aG9kKHN5bWJvbCkgeyBzZWxmLmluc3RhbmNlX3ZhcmlhYmxlX2 dldCh2YXIpIH0KCiAgICBpZiBj
    b252ZXJ0b3Iua2luZF9vZj8oU3ltYm9sKQogICAgICBkZWZpbm VfbWV0aG9kKHNldCkgeyB8KnZh
    bHwgdmFsID0gKnZhbDsgc2VsZi5pbnN0YW5jZV92YXJpYWJsZV 9zZXQodmFyLCB2YWwuc2VuZChj
    b252ZXJ0b3IpKSB9CiAgICBlbHNlCiAgICAgIGRlZmluZV9tZX Rob2Qoc2V0KSB7IHwqdmFsfCBz
    ZWxmLmluc3RhbmNlX3ZhcmlhYmxlX3NldCh2YXIsIGNvbnZlcn Rvci5jYWxsKCp2YWwpKSB9CiAg
    ICBlbmQKICBlbmQKICBwcml2YXRlIDphdHRyX2NvbnZlcnRvcg oKICBkZWYgYXR0cl9hY2Nlc3Nv
    cihibG9jaywgKnN5bWJvbHMpCiAgICBpZiBibG9jay5raW5kX2 9mPyhTeW1ib2wpCiAgICAgIHN5
    bWJvbHMudW5zaGlmdChibG9jaykKICAgICAgX19hdHRyX2FjY2 Vzc29yKCpzeW1ib2xzKQogICAg
    ZWxzaWYgYmxvY2sua2luZF9vZj8oSGFzaCkKICAgICAgYmxvY2 suZWFjaCB7IHxzeW1zLCBjb252
    ZXJ0b3J8IHN5bXMuZWFjaCB7IHxnZXR8IF9fYXR0cl9jb252ZX J0b3IoZ2V0LCBjb252ZXJ0b3Ip
    IH0gfQogICAgZWxzZQogICAgICBzeW1ib2xzLmVhY2ggeyB8Z2 V0fCBfX2F0dHJfY29udmVydG9y
    KGdldCwgYmxvY2spIH0KICAgIGVuZAogIGVuZAplbmQKCmlmIF 9fRklMRV9fID09ICQwCiAgcmVx
    dWlyZSAndGVzdC91bml0JwoKICBjbGFzcyBUZXN0Q29udmVydG 9ycyA8IFRlc3Q6OlVuaXQ6OlRl
    c3RDYXNlCiAgICBkZWYgdGVzdF9ub3JtYWwKICAgICAgZiA9IG 5pbAogICAgICBhc3NlcnRfbm90
    aGluZ19yYWlzZWQgZG8KICAgICAgICBpdGVtID0gQ2xhc3Mubm V3CiAgICAgICAgaXRlbS5pbnN0
    YW5jZV9ldmFsIGRvCiAgICAgICAgICBhdHRyX2FjY2Vzc29yID pjb3N0LCA6aXRlbSwgOm5hbWUK
    ICAgICAgICBlbmQKICAgICAgICBmID0gaXRlbS5uZXcKICAgIC AgICBmLml0ZW0gPSAiMyIKICAg
    ICAgICBmLm5hbWUgPSAiICBmb28gICIKICAgICAgICBmLmNvc3 QgPSAiMy41MiIKICAgICAgZW5k
    CiAgICAgIGFzc2VydF9lcXVhbCgiMyIsIGYuaXRlbSkKICAgIC AgYXNzZXJ0X2VxdWFsKCIgIGZv
    byAgIiwgZi5uYW1lKQogICAgICBhc3NlcnRfZXF1YWwoIjMuNT IiLCBmLmNvc3QpCiAgICBlbmQK
    CiAgICBkZWYgdGVzdF9wcm9jCiAgICAgIGYgPSBuaWwKICAgIC AgYXNzZXJ0X25vdGhpbmdfcmFp
    c2VkIGRvCiAgICAgICAgaXRlbSA9IENsYXNzLm5ldwogICAgIC AgIGl0ZW0uaW5zdGFuY2VfZXZh
    bCBkbwogICAgICAgICAgYXR0cl9hY2Nlc3NvciBwcm9jIHsgfH h8IHgudG9fZiB9LCA6Y29zdAog
    ICAgICAgICAgYXR0cl9hY2Nlc3NvciBwcm9jIHsgfHh8IHgudG 9faSB9LCA6aXRlbQogICAgICAg
    ICAgYXR0cl9hY2Nlc3NvciBwcm9jIHsgfHh8IHgudG9fcy5zdH JpcCB9LCA6bmFtZQogICAgICAg
    IGVuZAogICAgICAgIGYgPSBpdGVtLm5ldwogICAgICAgIGYuaX RlbSA9ICIzIgogICAgICAgIGYu
    bmFtZSA9ICIgIGZvbyAgIgogICAgICAgIGYuY29zdCA9ICIzLj UyIgogICAgICBlbmQKICAgICAg
    YXNzZXJ0X2VxdWFsKDMsIGYuaXRlbSkKICAgICAgYXNzZXJ0X2 VxdWFsKCJmb28iLCBmLm5hbWUp
    CiAgICAgIGFzc2VydF9lcXVhbCgzLjUyLCBmLmNvc3QpCiAgIC BlbmQKCiAgICBkZWYgdGVzdF9o
    YXNoCiAgICAgIGYgPSBuaWwKICAgICAgYXNzZXJ0X25vdGhpbm dfcmFpc2VkIGRvCiAgICAgICAg
    aXRlbSA9IENsYXNzLm5ldwogICAgICAgIGl0ZW0uaW5zdGFuY2 VfZXZhbCBkbwogICAgICAgICAg
    YXR0cl9hY2Nlc3NvciBbOmNvc3RdID0+IDp0b19mLCBbOml0ZW 1dID0+IDp0b19pLCBbOm5hbWVd
    ID0+IHByb2MgeyB8eHwgeC50b19zLnN0cmlwIH0KICAgICAgIC BlbmQKICAgICAgICBmID0gaXRl
    bS5uZXcKICAgICAgICBmLml0ZW0gPSAiMyIKICAgICAgICBmLm 5hbWUgPSAiICBmb28gICIKICAg
    ICAgICBmLmNvc3QgPSAiMy41MiIKICAgICAgZW5kCiAgICAgIG Fzc2VydF9lcXVhbCgzLCBmLml0
    ZW0pCiAgICAgIGFzc2VydF9lcXVhbCgiZm9vIiwgZi5uYW1lKQ ogICAgICBhc3NlcnRfZXF1YWwo
    My41MiwgZi5jb3N0KQogICAgZW5kCiAgZW5kCmVuZAo=

    --07557043-POCO-66642815--


    Austin Ziegler Guest

  12. #11

    Default Re: Managing metadata about attribute types

    On Wed, 5 Nov 2003 16:13:37 +0900
    Austin Ziegler <austin@halostatue.ca> wrote:

    <big snip>
    > It isn't inferior, and you don't need type info. Remember -- an
    > object should validate or transform its own data.
    <big snip>

    I'm trying to stay out of this because I mostly disagree. This
    however warrants addressing.

    This is demonstrably wrong. An object cannot validate and transform
    its own data in this context in any reasonably general manner. It's
    simple when you're addressing a few basic types... String, Float,
    Integer, Hash and Array.

    This isn't general, though. What if I want a Foo, and you give me a
    Bar? Foo was from one module (which shouldn't know about Bar), and
    Bar was from another module (which shouldn't know about Foo). There
    is there no #to_foo (which may be fortunate depending on your
    culinary preferences). This leaves us with only a few options:

    * We just don't allow it. This does no good for us.

    * We convert through an intermediary type. This is inefficient
    and may lose data or not work, either.

    * We decide the approach is wrong and do something else.

    Using #to_* methods are the ruby equivalent of type casting. The
    point in this case is not to _convert_ types, it's to provide the
    right type in the first place. Instead of giving the attribute a
    string and expecting it to be parsed, we want to create the desired
    type and hand that off.

    It has nothing to do with the #attr= function. Strict type checking
    at that point is merely a convenience. It's all about getting the
    input into a useful format without writing n^2 functions (for n
    classes). This is the primary reason I wrote StrongTyping in fact;
    the strict checking has merely helped with debugging a whole lot.

    --
    Ryan Pavlik <rpav@mephle.com>

    "Do not question wizards, for they are quick to
    turn you into a toad." - 8BT

    Ryan Pavlik Guest

  13. #12

    Default Re: Managing metadata about attribute types

    On Wed, 5 Nov 2003 14:16:53 +0900
    Simon Kitching <simon@ecnetwork.co.nz> wrote:
    > Hi Ryan,
    >
    > Thanks for your reply.
    >
    > MetaTags ([url]http://raa.ruby-lang.org/list.rhtml?name=metatags[/url]) may be what
    > I was looking for. I'll try to figure out exactly what it does over the
    > next few days.
    Hope you find it useful. You may still want to couple it with
    strongtyping, as this provides a really convenient way to do what you
    want (check desired types), but you can do it with metatags alone.

    I've thought about doing this, in fact, for "documenting" builtin
    classes and their methods before. You shouldn't have a problem
    modifying the existing method_info or class_info tagsets to handle
    this.
    > Mephle looks very interesting .. might have to look into it later on.
    It's another can of worms. I should have an app or two that uses it
    coming out soon, though.

    ttyl,

    --
    Ryan Pavlik <rpav@mephle.com>

    "Do not question wizards, for they are quick to
    turn you into a toad." - 8BT

    Ryan Pavlik Guest

  14. #13

    Default Re: Managing metadata about attribute types


    "Simon Kitching" <simon@ecnetwork.co.nz> schrieb im Newsbeitrag
    news:1067992395.2514.549.camel@PCSIMON.ecnnz.ecnet work.co.nz...
    > Hi,
    >
    > I'm porting the Apache Jakarta Commons Digester (written in Java) to
    > Ruby at the moment. This module processes xml in a rules-based manner.
    > It is particularly useful for handling complex xml configuration files.
    >
    > However some of the very nice features of this module depend upon being
    > able to introspect a class to find what attributes it has, and what
    > their datatypes are.
    >
    > Finding attributes on a Ruby class is simple (just look for "attr="
    > methods).
    That's not the best way. Better do "obj.instance_variables".
    > Unfortunately, determining what object types it is valid to
    > assign to that attribute is not so simple...
    It's impossible.
    > I was wondering if there were any other Ruby projects which have faced
    > this problem and come up with solutions? I would rather steal a solution
    > than invent one :-)
    Then why not postprocess the output of YAML on dumping and convert the XML
    to YAML on reading?

    Regards

    robert

    Robert Klemme Guest

  15. #14

    Default Re: Managing metadata about attribute types

    On Thu, 2003-11-06 at 16:27, Austin Ziegler wrote:
    On Thu, 6 Nov 2003 10:16:41 +0900, Simon Kitching wrote:
    >
    I hope you realise that the StockItem was just an example I made up out
    of thin air for the purposes of the discussion - it isn't a real class
    in use anywhere. Xmldigester is like xml-config; it is a library to
    configure any set of objects. The StockItem class is just one example.


    Here's a slightly more complex example, that might get away from the
    triviality of the StockItem's cost attribute example.

    class Weight
    def initialize(units, amount)
    @units = units
    @amount = amount
    end

    # other weight-related methods here, with defined behaviours
    # and associated contracts....
    end

    class StockItem
    # user contract: anything assigned to this attribute must behave
    # like a Weight object.
    attr_accessor :weight

    attr_accessor :name
    attr_accessor :cost # Float
    end

    <stock-item name="spanner" cost="12.50" weight="0.75 kg"/>


    Now as a programmer dealing with the above problem, I want to be able to
    tell xmldigester than when it encounters the <stock-item> tag it is to
    create a StockItem instance, then create a Weight instance and
    initialise it appropriately from a string, then assign that initialised
    object to the StockItem's weight attribute.

    The Java Digester version does this automatically, by determining that
    the StockItem has a "weight" attribute of type "Weight", and that there
    is a "weight" xml attribute (with a string value). It then invokes a
    table of data-conversion methods to convert the string to the target
    type; built-in types are already in the table, and user-specific types
    (like Weight) can be added as needed.

    You're suggesting that in the file which contains the "xml-parsing"
    code, I re-open the existing StockItem class, and use some approach like
    the attr_accessor modification to "wrap" the existing weight= code,
    resulting in something that effectively works like:

    class StockItem # reopen existing class
    alias :__weight= :weight=

    def weight=(param)
    weight2 = param.to_weight
    __weight = weight2
    end
    end

    and Weight doesn't have a to_weight method yet, so I'd need to reopen
    that class too:

    class Weight # reopen existing class
    def to_weight
    return self
    end
    end

    and finally add a method to String:

    class String # reopen string
    def to_weight
    # some code to instantiate a Weight object and
    # initialise it from the String's value
    end
    end

    Yes, I am now able to do this, which would indeed satisfy my
    requirements:

    # can now assign via strings
    stock_item.weight = '0.75 kg'
    and
    # can still use StockItem instances as per normal
    stock_item.weight = Weight.new('g', 750)


    It seems a lot of work, though. And I'm not sure I'm too fond of adding
    methods to the String class. Nor of the overhead that now exists on
    every assignment to stock_item.weight, though that's not so important.

    And what about if Weight is actually Acme::Warehouse::Weight?


    I'll give this approach some serious thought, though.

    Hmm .. libraries don't ever call "freeze" on their classes, do they?
    > s = StockItem.new
    > s.name = "Apple Pie" # An apple pie...
    > s.cost = 10 # Costs $10...
    > per_slice = s.cost / 8 # Split it eight ways...
    > puts per_slice # => 1
    >
    > Therefore, by simply *assuming* that you're getting an object that
    > can act like a float, you've introduced a huge error. Should I have
    > entered 10.0 as the price, or divided by 8.0? Either of those would
    > have guaranteed me a Float context in which type coercion will be
    > used to ensure a Float result. If, however, we had converted cost to
    > a float explicitly during assignment, this wouldn't even be an
    > issue. Without talking about Strings, we've already run into a
    > problem with StockItem's assumption of Float-ness.
    Yes, but as you noted yourself, you've violated the contract on the
    class. I haven't bothered to clutter my example with "defensive
    programming". I grant that you may be right that the cost method should
    call to_f on its parameter, so that Integers can also be passed. And
    maybe every method expecting a String parameter should call to_s on its
    parameter?

    Oh, unless nil is acceptable as a parameter, in which case it would be
    better to do:
    @name = name.to_s if name

    I wonder how many of the Ruby standard libraries do this? Certainly no
    attribute declared with "attr_reader" etc does.

    Hmm .. by the way, aren't you arguing against duck-typing here?
    If someone deliberately creates a type which is *like* a Float, then
    this code would force it to be a real float (assuming that to_f returns
    a real Float object).
    >
    > Compare the same Java:
    >
    > class StockItem {
    > String name;
    > float cost;
    >
    > void setName(String n) { name = n; }
    > void setCost(float c) { cost = c; }
    >
    > String getName() { return name; }
    > float getCost() { return cost; }
    > }
    >
    > In Java, it doesn't matter if you pass an int to setCost because the
    > compiler has already marked that as a float -- and it will do an
    > implicit conversion from int to float. (IIRC, that *won't* work in
    > Ada, which disallows implicit conversions.)
    Yes, but for the Java Digester library, implicit conversions are
    irrelevant. It doesn't try to pass a String to the setCost method and
    hope the compiler will insert the correct conversion code (it won't
    anyway). Instead, as described earlier, a table of "type conversion
    operations" is used to map from the input String to the target type,
    allowing any target type (such as Weight) to be correctly dealt with.
    >
    > The author of the StockItem class *should* have considered that any
    > numeric value could have been assigned -- and that integer math
    > wouldn't be a good idea.
    As above, I agree that for safety cost= could try to ensure the
    parameter passed to it complies with the contract. I still don't believe
    that it is mandatory for methods to enforce their contracts,
    though...user beware should be the motto, for performance and
    simplicity.
    > >> attr_accessor proc { |x| x.to_i }, :item_id
    > > That's some very cool code. I can feel my brain expanding just by
    > > looking at it! However I don't feel it does what I want, because
    > > this code actually changes the API of the target class, breaking
    > > all other code that accesses that same attribute thereafter.
    >
    > Actually, it doesn't change the API at all. It enforces the
    > documented constraints. It's the difference between early and late
    > detection.
    I didn't initially realise that if a real Float was passed to this
    method, then calling to_f on it is ok; it just returns the same object.
    >
    > [snip bean info stuff]
    >
    > I donno. That still doesn't feel very "Ruby" to me, and I personally
    > find both StrongTyping and MetaTag clunky, trying to solve things
    > that I'm not sure are best solved that way.
    Yes, I'm still worried that there is some obvious rubyish way to handle
    this. Maybe when I actually issue 0.1 of xmldigester (with whatever
    API), then start trying to build some apps with it some epiphany will
    happen.



    Cheers,

    Simon


    Simon Kitching Guest

  16. #15

    Default Re: Managing metadata about attribute types

    >
    >Hmm .. by the way, aren't you arguing against duck-typing here?
    >If someone deliberately creates a type which is *like* a Float, then
    >this code would force it to be a real float (assuming that to_f returns
    >a real Float object).
    Just weighing in with a small point, but
    1.0.to_f #=>1.0

    A type *like* a float responds to to_f, to give a float. For any type to act
    like a float, it must quack like a float, in the sense it gives a meaningful
    float when it hears to_f. #to_x messages are just messages. It is not
    casting - unless you are wacky enough to write a #to_f! method. Its asking
    the object to show its face as a float.

    David.

    David Naseby Guest

  17. #16

    Default Re: Managing metadata about attribute types

    On Thu, 2003-11-06 at 16:27, Austin Ziegler wrote:
    > On Wed, 5 Nov 2003 18:08:04 +0900, Ryan Pavlik wrote:
    > > Austin Ziegler <austin@halostatue.ca> wrote:
    > > <big snip>
    > >> It isn't inferior, and you don't need type info. Remember -- an
    > >> object should validate or transform its own data.
    > > <big snip>
    > > I'm trying to stay out of this because I mostly disagree. This however
    > > warrants addressing.
    > >
    > > This is demonstrably wrong.
    >
    > Actually, it's demonstrably correct. It's exactly to the point and
    > perfectly accurate regarding how one should deal with data in a
    > dynamically typed language such as Ruby. In Text::Format, I have a
    > method #hyphenator= which accepts any object that responds to
    > #hyphenate_to with an arity of 2 or 3. I explicitly reject any other
    > object. In documentation, I make it clear that #hyphenate_to should
    > return an array of two objects. In this way, I don't care what
    > *class* an object is, I just care that it's type is a hyphenator (as
    > defined above).
    >
    > > An object cannot validate and transform its own data in this
    > > context in any reasonably general manner.
    >
    > Your StrongTyping module doesn't help with this, either, Ryan. It's
    > not a conversion module.
    I don't think that the concept of strict typechecking deserves quite
    such a roasting :-)

    For me, when looking at a method like
    def foo(param)
    ...
    end
    the question is "what contract is the param required to adhere to?".
    And maybe "what is the contract of the returned object".

    If the code is strictly-typed, like
    void foo(Map map)
    end
    then I know exactly what contract the param must adhere to: it's
    documented in the javadoc on the Map class. [Isn't that the definition
    of "type"? A contract of behaviour?]

    Ok, there are flaws to strict typing. The first is that the contracts
    tend to over-specify. The foo method probably only wants a few of the
    methods from the Map class, but the concrete class of the object I pass
    must implement them *all*. In fact, the Java collections class has an
    ugly hack to resolve this issue: methods are allowed to throw an
    Unsupported exception, which means an object might only partially fulfil
    the required contract. However 90% is probably good enough.

    And for java's Object-based collections, you lose part of the contract:
    what is the object type *in* the map. Generics (templates for Java) will
    resolve this issue (I hope).

    In programming languages which are always distributed with
    non-obfuscated source code, the source can be inspected to determine the
    contract. This isn't too bad a solution, provided the library author
    writes clean code and comments it well.

    Some developers are well enough disciplined to put the contract in
    comments. This is fairly rare, though. And there is no guarantee that
    those comments are actually correct and up-to-date.

    And then there is "try it and see", where the user has to discover the
    contract by trial and error. I'm not so fond of this.

    In the end, the problem is simply one of human<->human communication.
    The author of a library needs to tell the user of the library about
    various contracts. Strict typing is one end of the spectrum of this
    communication. It results in well-specified code, but at the cost of
    extra developer labour. Ruby is the other end; it results (generally) in
    completely unspecified code (see "read the source" above) but imposes
    little overhead on the developer.

    Compiler-assisted programming, where the compiler tells the user when
    they got it wrong is just a bonus. I wouldn't miss this as much as the
    lack of *communication* about types.

    I don't think that a true measure of the effectiveness of strict typing
    can be had by saying "I wrote a big application and didn't need strict
    typing". I suggest you try *using* a big library someone else wrote, and
    see if you miss it :-). Then deliver that app to a customer and see if
    they turn up any cases where you made a mistake about the contract of a
    method or parameter.

    Obviously I'm biased; my experience is mainly in strictly-typed
    languages. For smallish apps I can clearly see the benefits of Ruby.

    In fact, given a combination of good unit-tests and well-modularised
    code (so I can see all the places an object is manipulated and therefore
    deduce its contract), I could be convinced of Ruby for large projects
    too. But I think I will always wish that the *type* of parameters (and
    return values!) were simply documented via type declarations like Java.


    Cheers,

    Simon


    Simon Kitching Guest

  18. #17

    Default Re: Managing metadata about attribute types

    On Thu, 6 Nov 2003 12:27:43 +0900
    Austin Ziegler <austin@halostatue.ca> wrote:
    > On Wed, 5 Nov 2003 18:08:04 +0900, Ryan Pavlik wrote:
    <snip>
    > > This is demonstrably wrong.
    >
    > Actually, it's demonstrably correct. It's exactly to the point and
    > perfectly accurate regarding how one should deal with data in a
    > dynamically typed language such as Ruby. In Text::Format, I have a
    > method #hyphenator= which accepts any object that responds to
    > #hyphenate_to with an arity of 2 or 3. I explicitly reject any other
    > object. In documentation, I make it clear that #hyphenate_to should
    > return an array of two objects. In this way, I don't care what
    > *class* an object is, I just care that it's type is a hyphenator (as
    > defined above).
    This is a specific case that does not generalize.
    > > An object cannot validate and transform its own data in this
    > > context in any reasonably general manner.
    >
    > Your StrongTyping module doesn't help with this, either, Ryan. It's
    > not a conversion module.
    Actually it does, but not the way you think. The point in using the
    ST module is not the type verification; many ruby people get too hung
    up on the type checking bit.

    The real area of interest is the fact it _documents_ what you want;
    you can ask it what type it is expecting with the various query
    functions the ST module provides.
    > > It's simple when you're addressing a few basic types... String,
    > > Float, Integer, Hash and Array.
    >
    > Not really. If I were to do the following:
    <big typechecking examples snipped>
    > If you know you're going to be doing something that could leave
    > things in an intermediate state, ensure that they don't get left in
    > such a state. The bridge is closed when you pass "m" as a string.
    Again, this misses the point. The original question was "how do I ask
    what a particular thing wants?" My answer was to use the ST module,
    because you do exactly that:

    class Foo
    def foo=(x); expect(x, Numeric); @x=x; end
    end

    Or more conveniently:

    class Foo
    attr_accessor_typed Numeric, :foo
    end

    Now you can simple say:

    foo = Foo.new
    t = get_arg_types(foo.method(:foo=)) # => [[Numeric]]

    Now, how is this useful? We can do "third-party" input processing,
    where it properly belongs. Having this code in either the source
    class (String) or the destination class (for the XML) is wrong, since
    it results in unnecessary coupling, and thus limits extensibility.

    As a third party, we can have an interface for extending the input
    processing from any position.
    > > * We just don't allow it. This does no good for us.
    >
    > But this is *exactly* what StrongTyping does, Ryan. It doesn't allow
    > types that it doesn't know how to deal with.
    No, Austin, this isn't what it's about. This is not about type
    checking arguments passed to a method---that's just a handy side
    effect in this case. It's about querying beforehand.

    <snip>
    > > Using #to_* methods are the ruby equivalent of type casting. The
    > > point in this case is not to _convert_ types, it's to provide the
    > > right type in the first place. Instead of giving the attribute a
    > > string and expecting it to be parsed, we want to create the
    > > desired type and hand that off.
    >
    > Oh, bollocks. Item's @cost is expected to work as a float. Not an
    > integer, a float. If I want to ensure that it works as a float,
    > Item#cost= should attempt to *make* the provided parameter into a
    > float. If someone is going to pass me something that can't be
    > converted to what I expect, they're going to get an error. If I'm
    > expecting a Foo, though, then I should probably do something like:
    >
    > def foo=(x)
    > @x = Foo(x)
    > end
    Er, that works in C++, but not ruby, unless you define a Foo()
    function. I'm not sure ruby will allow both a Foo class and a Foo
    function, but either way it's not any sort of working standard.

    However, given Foo(x) makes a Foo out of x, this is definitely not a
    good solution. We want a Foo. Any Foo should work...

    class Bar < Foo
    :
    end

    ...even if it's a Bar. Inheritence is one of the big three of OOP;
    doing the sort of re-creation you have here defeats the point. Even
    C++ typecasting preserves the identity of a thing.
    > That's *if* a Foo can be converted from other types. Or, maybe, I'm
    > just looking for a particular method call, in which case I can
    > defensively program as I did with Text::Format. Can it still be
    > bitten by someone who accepts the parameters in a different order or
    > expects different things? Sure. But that's not exactly something
    > that I can program against in any case ... unless I'm using strong
    > typing, and IMO that does the wrong thing.
    I'm not sure what you mean by "the wrong thing"; since you've been
    focusing solely on strict type checking, that may be it. I fail to
    see how this is "the wrong thing", though.

    In any case, you could conceivably use ST to handle this case. I've
    been contemplating a module that augments this and provides call
    context and semantics in addition to types. Duck typing people may
    like this more, since it would do implicit type conversions. You
    wouldn't care what arguments a method took; you'd provide it with the
    data at hand, coupled with its semantic documentation:

    def foo(*args)
    x, y = find_in_context([:x, Float, :point],
    [:y, Float, :point])
    :
    end

    We could call it in a number of ways:

    # Specify semantics explicitly:
    foo(:x => 1, :y => 2)

    # "Point" would have semantic tagging for :point, :x, and :y:
    point = Point.new(1, 2)
    foo(point)

    # Have one in context:
    context_push Point.new(0, 0)

    Circle.new # Both gather their point
    foo # from the existing context

    Ambiguity and insufficient data would both raise exceptions.
    Conceivably these could raise to the user level, and have the user
    provide discernment or additional data.

    Programming with a context pool is exceedingly useful in a number of
    areas, but I'm straying far from the original point. One of the bits
    of information here is still type, and for the _same_reason_ I
    discussed above: so a third party can query and handle things.
    > Note that I've done much the same thing with Ruwiki recently -- I've
    > abstracted out what a Ruwiki::Wiki::Token needs to know into a
    > Handler. It responds to certain methods. As the needs for what a
    > Wiki Token needs to know increase, the Handler will increase as
    > well. I'm not even checking to see if the token handler is a
    > specific class. I just expect that it will respond to those methods.
    I tried a similar thing once (actually it was quite a bit different in
    application, but similar in form), but found it leads to far too much
    information redundancy. I should be able to say, once, "this is what
    this means, this is what this wants, this is what it does". I
    shouldn't have to write it again for every class. Granted, if you
    only have one class (Ruwiki::Wiki::Token), then it's not much of a
    problem.

    But the ability to query the type it wants lets you solve the problem
    in a general manner, so you don't have to do it again for _any_ class,
    and you can extend the general case with a minimal amount of
    additional code.

    Basically, when I have 200 classes, I don't want another 200 classes
    to do interpretation. It makes the idea of writing that 201st class
    not seem very pleasant.
    > As Rich Kilmer said, are you checking for behaviour or namespace?
    OK, let's look at it this way. Say we write things in a duck-typed
    manner. We could provide something similar to ST for efficient
    checking:

    def foo(h)
    expect_duck h, :[], :[]=
    :
    h["something"] = ...
    :
    end

    Right? We treat it not by type, but what it acts like. It provides
    us with #[] and #[]=, so (ignoring any semantic issues) it's enough of
    a duck for us.

    Now, this may begin to get tedious:

    def foo(a, b)
    expect_duck a, [:first, :last, :[], :each],
    b, [:first, :last, :[], :each]
    :
    end

    So, after a time, we start defining common sets so we don't have to do
    that every time:

    ARRAY_DUCK = [:first, :last, :[], :each]

    def foo(a, b)
    expect_duck a, ARRAY_DUCK, b, ARRAY_DUCK
    :
    end

    Now, you probably see where I'm going with this. Where I've already
    gone, actually. This is a simple reinvention of classes, just lacking
    important semantic information.

    You can also treat a class (or module) itself as the behavioral
    specification, and with it you gain the important semantic
    differentiation between the proverbial ducks and platypuses.
    You can simply ask, "is this a Foo?", and know that it will act like
    you want. When you make a thing that acts like a Foo, you can
    subclass or include Foo, to show what you mean.
    > > It has nothing to do with the #attr= function. Strict type
    > > checking at that point is merely a convenience. It's all about
    > > getting the input into a useful format without writing n^2
    > > functions (for n classes). This is the primary reason I wrote
    > > StrongTyping in fact; the strict checking has merely helped with
    > > debugging a whole lot.
    >
    > It has *everything* to do with the #attr= function in the case given.
    > The OP is dealing with an XML document, which means that everything
    > is a string and #to_f works.
    Austin, as above, not every type may be a simple string, float,
    integer, array, or hash. There are no other standard #to_* functions.
    (Even array and hash are pushing that one.) It would require either
    String know how to interpret every type, or the procs you write
    know how to interpret every type. Eventually you're going to want to
    add more types, and this method doesn't really allow for inheritence,
    and it's not otherwise very extensible.

    <snip>
    > The problem here is ultimately that your objects have to know what
    > they expect and how they are expected to be used *in general*. If
    > you've got Item#cost, you can expect that it will be used in
    > mathematic operations. You'll probably do such operations yourself.
    > So, why not do some sort of conversion on the data to make it into
    > what you expect it will be when you're defining your attribute
    > accessor?
    The question is where it should be done. This is a perfect example.
    Money should never (ever!) be done with Floats. People don't like it
    when they lose money due to lack of precision.

    I've had to write a Currency module which uses integers, but provides
    various currency formats and manipulations. If we had Item#cost, we
    could not solve the problem with #to_* conversions:

    class Item
    attr_accessor proc { |x| x.to_?? } :cost
    end

    What do we convert to? What we want is Currency---it doesn't matter
    what type. It could be USD, Euros, Yen, or whatever. There is no
    general #to_currency, and we can't just convert it to an integer,
    because we lose what the integer means (200 USD is not Y200). We
    don't really have anything to demand that it responds to; currency is
    more or less a number like anything else, it just has a unit attached.

    With StrongTyping, it's easy:

    class Item
    attr_accessor Currency, :cost
    end

    Now, _before_ we take "$200.05" and assign it to cost, the ST module
    allows us to ask what it wants, and we can, as a third party, know how
    to convert input type (a String) to the desired type (Currency).
    (In this case, for a general solution, I'd recommend something like
    the conversion table I discussed in [ruby-talk:74206].)
    > A lot of people coming from strict typing backgrounds forget that
    > this is done implicitly by the compiler ... or rather, because the
    > type is strictly specified it isn't necessary to do such
    > conversions. They're done automatically when the types can be
    > converted.
    Yes, but they're both standard and extensible. In C++ I can do:

    operator int() {
    return (int)this->something;
    }

    and when I "(int)thing", that routine gets called. OTOH, I don't
    think this is really the right solution, as this sort of thing is too
    coupled with static typing, and static typing and OOP have no business
    being in the same language.
    > P.S. It was suggested that:
    > attr_accessor proc { |x| x.to_f }, :cost
    > is overkill. Thus, the version I attached last night includes:
    > attr_accessor [:cost] => :to_f
    > I may add other enhancements to that mechanism (as providing
    > multiple method symbols in an array and chaining them).
    <snip>

    Now, with all I've said above, I haven't really addressed
    attr-with-proc stuff. Actually, I think it'd be a pretty neat idea to
    be able to tag extra code onto attributes without having to do a
    full-out def.

    I don't think it's the right solution to this problem, though.

    --
    Ryan Pavlik <rpav@mephle.com>

    "Do not question wizards, for they are quick to
    turn you into a toad." - 8BT

    Ryan Pavlik Guest

  19. #18

    Default Re: Managing metadata about attribute types

    <snip lots>
    > ARRAY_DUCK = [:first, :last, :[], :each]
    >
    > def foo(a, b)
    > expect_duck a, ARRAY_DUCK, b, ARRAY_DUCK
    > :
    > end
    >
    >Now, you probably see where I'm going with this. Where I've already
    >gone, actually. This is a simple reinvention of classes, just lacking
    >important semantic information.
    >
    >You can also treat a class (or module) itself as the behavioral
    >specification, and with it you gain the important semantic
    >differentiation between the proverbial ducks and platypuses.
    >You can simply ask, "is this a Foo?", and know that it will act like
    >you want. When you make a thing that acts like a Foo, you can
    >subclass or include Foo, to show what you mean.
    Asking if its a Foo sets in stone the use of your code, as you say. It
    disambiguates, and documents at the same time, again, as you said.

    But to stretch every analogy to breaking, seeing as you brought them up,
    imagine its the 17th century and you write your code to expect a Duck. The
    conditions of swimming and having a bill could apply to nothing else.. err..
    except some other waterfowl! OK.. so you generalise back and create a
    Waterfowl class, and you are all documented and happy that your code uses
    Waterfowl, and nothing but Waterfowl.

    Then the 18th century rolls around, and the platypus emerges. Its certainly
    not a Waterfowl, but it does have a bill and swims, and it would benefit
    from your code. But it is blocked from using it. Your strong typing reduced
    your codes benefit to a use you could not have *possibly* have predicted.

    Dynamic duck typing allows for serendipity at the cost of some
    self-documentation.

    David.

    David Naseby Guest

  20. #19

    Default Re: Managing metadata about attribute types

    On Thu, 6 Nov 2003 15:05:58 +0900
    Simon Kitching <simon@ecnetwork.co.nz> wrote:

    <snip>
    > I don't think that the concept of strict typechecking deserves quite
    > such a roasting :-)
    Yeah same, I'm not sure why it's a big deal... it's been pretty
    helpful in debugging. If I specify something it doesn't like, I see
    exactly where it came from. There are cases where, for instance, I
    might assign an attribute, or add something to a list that gets used
    later, only to have an error occur in never-never land. No indication
    as to who the offending party was.
    > For me, when looking at a method like
    > def foo(param)
    > ...
    > end
    > the question is "what contract is the param required to adhere to?".
    > And maybe "what is the contract of the returned object".
    >
    > If the code is strictly-typed, like
    > void foo(Map map)
    > end
    > then I know exactly what contract the param must adhere to: it's
    > documented in the javadoc on the Map class. [Isn't that the definition
    > of "type"? A contract of behaviour?]
    As in my (just) previous message, this is exactly what it is. Even if
    you say "I want this thing to respond to #[] and #[]=", you could
    label that set... and it basically becomes a class.
    > Ok, there are flaws to strict typing. The first is that the contracts
    > tend to over-specify. The foo method probably only wants a few of the
    > methods from the Map class, but the concrete class of the object I pass
    > must implement them *all*. In fact, the Java collections class has an
    > ugly hack to resolve this issue: methods are allowed to throw an
    > Unsupported exception, which means an object might only partially fulfil
    > the required contract. However 90% is probably good enough.
    This is where mixins are nice. You can do "microtyping" (is that a
    word yet?), and pull in each set of interfaces. This has the added
    benefit of attached semantics, so you know that your #[] isn't a call
    to a block, but an array dereference, for instance.

    Most of the time you don't even need this... it happens naturally with
    superclasses.
    > And for java's Object-based collections, you lose part of the contract:
    > what is the object type *in* the map. Generics (templates for Java) will
    > resolve this issue (I hope).
    Generics sound too much like templates to me. IMO, templates,
    generics, and the like, are all hacks to get around the fact the
    language is static.

    Personally, I think that if you want something that's not a generic
    array, you should just make a subclass that filters its contents.
    I realize that this isn't easy in ruby for many of the base classes,
    but it's a simple and effective solution.

    Instead of making subclasses, you could even just test the filter.
    There are other related solutions.
    > In programming languages which are always distributed with
    > non-obfuscated source code, the source can be inspected to determine the
    > contract. This isn't too bad a solution, provided the library author
    > writes clean code and comments it well.
    <snip>
    > In the end, the problem is simply one of human<->human communication.
    > The author of a library needs to tell the user of the library about
    > various contracts. Strict typing is one end of the spectrum of this
    > communication. It results in well-specified code, but at the cost of
    > extra developer labour. Ruby is the other end; it results (generally) in
    > completely unspecified code (see "read the source" above) but imposes
    > little overhead on the developer.
    I generally don't like to have to do things that the computer could
    just as easily do a better job of, with information I need to give it
    anyway. The extra work on my part of entering the classname here and
    there isn't so strenuous and labor-intensive that it cuts into
    productivity. Debugging and copying information, though, does.

    My theory is that I should only have to tell the computer _once_, in
    _one_ place, what I mean, and it should be able to use that
    information repeatedly in as broad a manner as possible. Thus, if I
    specify my method wants a Date object, it should do everything from
    making sure I get one to automatically providing the user with the
    appropriate widget.
    > Compiler-assisted programming, where the compiler tells the user when
    > they got it wrong is just a bonus. I wouldn't miss this as much as the
    > lack of *communication* about types.
    Basically, I agree. The reason I wrote ST was not just because I was
    nervous about getting types wrong, it was because I needed to document
    what those types were, in a manner the code could ask itself about
    them. As above, the extra checking has improved debugging time
    drastically, and that's definitely a bonus.
    > I don't think that a true measure of the effectiveness of strict typing
    > can be had by saying "I wrote a big application and didn't need strict
    > typing". I suggest you try *using* a big library someone else wrote, and
    > see if you miss it :-). Then deliver that app to a customer and see if
    > they turn up any cases where you made a mistake about the contract of a
    > method or parameter.
    I concur. It's not _necessary_, in the sense it can't be done
    without. I could write a huge application with it, and then remove it
    later, and the application would still run.

    That, of course, is not the point.
    > Obviously I'm biased; my experience is mainly in strictly-typed
    > languages. For smallish apps I can clearly see the benefits of Ruby.
    I am lax with strick checking with many scripts and modules; usually
    this is because it doesn't matter, and I won't need to query things
    anyway, but I've often regretted it as well.
    > In fact, given a combination of good unit-tests and well-modularised
    > code (so I can see all the places an object is manipulated and therefore
    > deduce its contract), I could be convinced of Ruby for large projects
    > too. But I think I will always wish that the *type* of parameters (and
    > return values!) were simply documented via type declarations like Java.
    This will work, but I don't have like it---redundant work just diverts
    me from the problem at hand. A thousand programmers having to
    re-deduce the contract is a lot of wasted time.

    --
    Ryan Pavlik <rpav@mephle.com>

    "Do not question wizards, for they are quick to
    turn you into a toad." - 8BT

    Ryan Pavlik Guest

  21. #20

    Default Re: Managing metadata about attribute types

    On Thu, 6 Nov 2003 12:45:39 +0900
    [email]dblack@wobblini.net[/email] wrote:

    <snip>
    > I believe that's by design; as I understand it, the StrongTyping
    > module performs parameter gatekeeping based exclusively on the
    > class/module ancestry of an object (the namespaces to which it
    > belongs, as Rich and Chad were discussing), not on what the object
    > actually can do. This means, as you say, that objects which might fit
    > the bill may not get through, if their class/module ancestry is wrong,
    > and also that objects which do not fit the bill can get through -- for
    > example:
    <snip>

    This is the fundamental philosophical disagreement, or
    miscommunication, or what have you. If an object fits the bill, and
    its class/ancestry is wrong, then there is a error in design.
    It should not be the case that this happens, or you have found an
    error in your code.

    I realize not all of Ruby is documented in this manner; that's a
    simple matter to change. A few smaller modules would solve this; for
    instance, Set, HashedSet, IndexedSet, etc. Array would be an
    IndexedSet; modules such as CGI would include HashedSet. Then you
    could ask for the simple behavioral pattern you desire, and know that
    you have it. You would further be assured that this #[] means what
    you want it to.

    This isn't really any different than duck typing, except you're just
    making sure that it really does quack, it doesn't just have a bill.

    --
    Ryan Pavlik <rpav@mephle.com>

    "Do not question wizards, for they are quick to
    turn you into a toad." - 8BT

    Ryan Pavlik Guest

Posting Permissions

  • You may not post new threads
  • You may post replies
  • You may not post attachments
  • You may not edit your posts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139