Ask a Question related to Ruby, Design and Development.
-
Simon Kitching #1
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
-
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.... -
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... -
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 -
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... -
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... -
james@rubyxml.com #2
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
-
Ryan Pavlik #3
Re: Managing metadata about attribute types
On Wed, 5 Nov 2003 09:38:16 +0900
Simon Kitching <simon@ecnetwork.co.nz> wrote:
<snip>> Hi,
Yep. I'll take this opportunity to shamelessly plug some modules. I> 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 :-)
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
-
Ara.T.Howard #4
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
-
Chad Fowler #5
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
-
Austin Ziegler #6
Re: Managing metadata about attribute types
On Wed, 5 Nov 2003 09:38:16 +0900, Simon Kitching wrote:
You may want to note xml-configfile in addition to XMLDigester that> 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.
James Britt mentioned.
[url]http://raa.ruby-lang.org/list.rhtml?name=xml-configfile[/url]
Aside from Ryan Pavlik's StrongTyping module, I'm not sure that this> 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...
is absolutely necessary. See below for a bit more information.
I frankly don't see a reason to worry about this.> 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
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
-
Simon Kitching #7
Re: Managing metadata about attribute types
On Wed, 2003-11-05 at 17:09, Austin Ziegler wrote:
Hi Austin,>
> class StockItem
> attr_accessor :name # Defaults to String
> attr_reader :cost # Returns cost
> def cost=(x)
> @cost = x.to_f
> end
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
-
Simon Kitching #8
Re: Managing metadata about attribute types
On Wed, 2003-11-05 at 17:09, Chad Fowler wrote:
Yes. I'm not strictly looking for "the type of the target attribute". As> 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:
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.
Yep. I can't see how to offer what I want without some type info,> 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.
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 :-).
One of the xmldigester goals is *not* to require an XML Schema.>
> 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.
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 ;-)
Yep. Absolutely.> What you're really looking for is convenience (as opposed
> to "type safety").
Or go> You want to be able to add 1 + 1 and not end up with 11.
item.cost = some_value
and not have an exception thrown later when the class goes to use cost
in some arithmetic operation.
Yep. Tedious indeed. And as mentioned in my reply to Austin, it is a> 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
goal to instantiate and initialise objects without requiring any changes
to their code. Digester manages this fine.
The problem is : in order to automatically generate the filters, I need>
> 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.
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...
That may have been more information than I needed to know :-)> And now, the cold medication sets in....
Good afternoon...>
>
> Good night,
> Chad
>
Simon
Simon Kitching Guest
-
Simon Kitching #9
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
-
Austin Ziegler #10
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.You're right, they shouldn't. But if your warehouse management> 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.
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.
Not really. See below for one option. The way that I've implemented> In addition, the approach you suggest is quite labour-intensive;
> for every attribute, a "wrapper" method needs to be written.
this makes it easy to drop into place.
Ryan's MetaTags really does not do anything different than I'm> 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.
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:It isn't inferior, and you don't need type info. Remember -- an> Yep. I can't see how to offer what I want without some type info,>> 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.
> 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 :-).
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.
Digest manages this because the objects in Java automatically take> 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.
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.
Again, not really. The class you're building has to know what it> 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!
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.
But defining the Person class to convert its "age" parameter into an> 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...
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
-
Ryan Pavlik #11
Re: Managing metadata about attribute types
On Wed, 5 Nov 2003 16:13:37 +0900
Austin Ziegler <austin@halostatue.ca> wrote:
<big snip><big snip>> It isn't inferior, and you don't need type info. Remember -- an
> object should validate or transform its own data.
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
-
Ryan Pavlik #12
Re: Managing metadata about attribute types
On Wed, 5 Nov 2003 14:16:53 +0900
Simon Kitching <simon@ecnetwork.co.nz> wrote:
Hope you find it useful. You may still want to couple it with> 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.
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.
It's another can of worms. I should have an app or two that uses it> Mephle looks very interesting .. might have to look into it later on.
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
-
Robert Klemme #13
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...That's not the best way. Better do "obj.instance_variables".> 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).
It's impossible.> Unfortunately, determining what object types it is valid to
> assign to that attribute is not so simple...
Then why not postprocess the output of YAML on dumping and convert the XML> 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 :-)
to YAML on reading?
Regards
robert
Robert Klemme Guest
-
Simon Kitching #14
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?
Yes, but as you noted yourself, you've violated the contract on the> 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.
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).
Yes, but for the Java Digester library, implicit conversions are>
> 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.)
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.
As above, I agree that for safety cost= could try to ensure the>
> 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.
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.
I didn't initially realise that if a real Float was passed to this>> > That's some very cool code. I can feel my brain expanding just by> >> attr_accessor proc { |x| x.to_i }, :item_id
> > 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.
method, then calling to_f on it is ok; it just returns the same object.
Yes, I'm still worried that there is some obvious rubyish way to handle>
> [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.
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
-
David Naseby #15
Re: Managing metadata about attribute types
>
Just weighing in with a small point, but>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).
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
-
Simon Kitching #16
Re: Managing metadata about attribute types
On Thu, 2003-11-06 at 16:27, Austin Ziegler wrote:
I don't think that the concept of strict typechecking deserves quite> On Wed, 5 Nov 2003 18:08:04 +0900, Ryan Pavlik wrote:>> > Austin Ziegler <austin@halostatue.ca> wrote:
> > <big snip>> > <big snip>> >> It isn't inferior, and you don't need type info. Remember -- an
> >> object should validate or transform its own data.
> > 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.
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
-
Ryan Pavlik #17
Re: Managing metadata about attribute types
On Thu, 6 Nov 2003 12:27:43 +0900
Austin Ziegler <austin@halostatue.ca> wrote:
<snip>> On Wed, 5 Nov 2003 18:08:04 +0900, Ryan Pavlik wrote:This is a specific case that does not generalize.>> > 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).
Actually it does, but not the way you think. The point in using the>> > 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.
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.
<big typechecking examples snipped>>> > 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:
Again, this misses the point. The original question was "how do I ask> 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.
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.
No, Austin, this isn't what it's about. This is not about type>> > * 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.
checking arguments passed to a method---that's just a handy side
effect in this case. It's about querying beforehand.
<snip>Er, that works in C++, but not ruby, unless you define a Foo()>> > 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
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.
I'm not sure what you mean by "the wrong thing"; since you've been> 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.
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.
I tried a similar thing once (actually it was quite a bit different in> 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.
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.
OK, let's look at it this way. Say we write things in a duck-typed> As Rich Kilmer said, are you checking for behaviour or namespace?
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.
Austin, as above, not every type may be a simple string, float,>> > 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.
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 question is where it should be done. This is a perfect example.> 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?
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].)
Yes, but they're both standard and extensible. In C++ I can do:> 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.
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.
<snip>> 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).
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
-
David Naseby #18
Re: Managing metadata about attribute types
<snip lots>
Asking if its a Foo sets in stone the use of your code, as you say. It> 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.
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
-
Ryan Pavlik #19
Re: Managing metadata about attribute types
On Thu, 6 Nov 2003 15:05:58 +0900
Simon Kitching <simon@ecnetwork.co.nz> wrote:
<snip>Yeah same, I'm not sure why it's a big deal... it's been pretty> I don't think that the concept of strict typechecking deserves quite
> such a roasting :-)
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.
As in my (just) previous message, this is exactly what it is. Even if> 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?]
you say "I want this thing to respond to #[] and #[]=", you could
label that set... and it basically becomes a class.
This is where mixins are nice. You can do "microtyping" (is that a> 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.
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.
Generics sound too much like templates to me. IMO, templates,> 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, 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.
<snip>> 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.I generally don't like to have to do things that the computer could> 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.
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.
Basically, I agree. The reason I wrote ST was not just because I was> 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.
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 concur. It's not _necessary_, in the sense it can't be done> 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.
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.
I am lax with strick checking with many scripts and modules; usually> Obviously I'm biased; my experience is mainly in strictly-typed
> languages. For smallish apps I can clearly see the benefits of Ruby.
this is because it doesn't matter, and I won't need to query things
anyway, but I've often regretted it as well.
This will work, but I don't have like it---redundant work just diverts> 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.
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
-
Ryan Pavlik #20
Re: Managing metadata about attribute types
On Thu, 6 Nov 2003 12:45:39 +0900
[email]dblack@wobblini.net[/email] wrote:
<snip><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:
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



Reply With Quote

