What a vigorous discussion I seem to have triggered
It's really nice to see so many people interested in the topic - thanks
to all who replied.
Rather than reply individually to the several emails which raise
interesting points, I'll try to gather all the different bits here.
I really am interested in the points raised, and am definitely still in
the learning phase with Ruby. So all of the statements below should
really be prefaced with "I think", "It seems to me", "Do you think that
... is right?". However that would double the size of this email. Please
assume all below is tentative, and that comments/corrections are
welcome.
And I hope those people who "really wanted to stay out..." don't stay
out and chip in. I'm interested at the very least!
====
Re "xml-config" module, raised by Austin: there are significant
differences between the "xml-config" and "xmldigester" approaches.
Which is "better" will depend on circumstances and developer taste. The
most significant differences are:
(a)
Xml-config first builds a complete representation of the input xml in
memory, then starts extracting data. For large xml files this is not a
good idea. Xmldigester is "event-based", so the input xml does not have
to be completely loaded into memory.
(b)
I *believe* that the xmldigester rules-based approach will take less
client code, and will bind the "parsing" program less tightly to the API
of the objects being built than the xml-config approach.
(c)
If building inter-object relationships that are more complex than simple
parent/child references, the xml-config approach may prove easier. There
are some tricks that can be played with xmldigester (eg a common
"registry" object used to resolve relations), but having the entire xml
document available (DOM-style) can allow things that an event-style
approach cannot.
(d)
The xmldigester event-based approach is likely to be faster.
Of course the best test of all the above opinions is actually to create
the code, then compare the two. Once I have xmldigester knocked into
reasonable shape, I might port the xmldigester examples to xml-config
(and vice-versa) to see if any of the above is true!
Regardless of the results, I think that both approaches have their
place.
====
Regarding whether the target class should be responsible for accepting a
string and doing the conversion...
I think it is definitely *not* the receiving classes' responsibility to
do the conversion.
Here's my original class, with the initial implicit assumptions spelled
out more clearly as comments.
# Ruby
class StockItem
# contract with user: any value assigned to name must
# act like a string
attr_accessor :name
# contract with user: any value assigned to cost must act
# like a float object.
attr_accessor :cost
end
Isn't this a valid API for a class to provide? As far as the author of
StockItem is concerned, cost is a float.
I don't see why the author of StockItem should even consider the
possibility that a string could be assigned to cost; that would violate
the contract of the class, so any program that does so can damn well
take the consequences
. The StrictTyping module can enforce this, but
perhaps does so over-eagerly, as it doesn't allow "duck-typing" ie
objects which aren't Float objects but do behave like them.
Now I happen to want to configure this object based on some string-typed
information. But that's my problem, not the problem of the author of the
StockItem class. And if I wanted to use ASN.1 format input to initialise
an instance of StockItem, then it is still my responsibility to convert
the ASN.1 representation to an appropriate type before assigning to
cost, not the StockItem class' responsibility to understand ASN.1 format
input.
Ok, with Ruby's "open" classes, I can alias the original cost= method
and insert some wrapper code. But I will have to restore the original
method after parsing is complete, otherwise during the real "running"
part of the program, the StockItem's cost= method won't behave like
other classes expect it to.
Not to mention that writing those "conversion" methods by hand is ugly.
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.
The StockItem's contract clearly states that it only accepts Float types
for the cost attribute. It doesn't actually need to enforce its data
integrity - it is the calling code's responsibility to use StockItem
correctly.
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.
The data conversion clearly has to be done somewhere, but I would like
it to be done separately from the target class so as not to muck around
with its API.
Here's the "conversion" code extracted out into a helper class:
def StockItemHelper
def StockItemHelper.cost=(stock_item, str)
stock_item.cost = str.to_f
end
end
In fact, why not use the Java convention and call it StockItemBeanInfo?
Applying a modified version of your attr_accessor code, this could be
written more succinctly as the following, generating effectively the
same code as shown above:
def StockItemBeanInfo
attr_from_str :name, String
attr_from_str :cost, Float
end
However I can also use something like Ryan's MetaTag syntax to write
this. I'm not sure which syntax is more convenient.
desc = <<-END
!class StockItem
!name String
!cost Float
END
# parse the string and dynamically create a wrapper class
beanInfo = createBeanInfoClass(desc)
# because cost was declared as a Float in the MetaTag string,
# the beanInfo class knows to convert the second (string) param
# to a float.
beanInfo.set_cost(stock_item, '3.50')
As you can see, I'm not interested in "type strictness" at all.
What I need is simply "what type of object should I generate in order to
be able to validly assign to cost without violating the API contract of
the StockItem class"...
Changing the StockItem class contract is one solution, but that screws
up all other code that really depended on the original contract being
valid.
Oh, and what if the target attribute is a "Date" class, and I want to
globally control the way string->date mapping works? If it is
distributed across every class that has a Date attribute that is much
trickier to handle than if I somehow know that classes X, Y and Z have
date attributes and the xmldigester code does the string->date
conversions before the assignment.
====
From Ryan:
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.
Yep, that's exactly how I see it.
However I don't want the "type enforcement at runtime" feature of
StrongTyping, and I want to avoid changing the target class' behaviour
in any significant way. Is it possible to get the "type info" part of
StrongTyping without the "type enforcement"?
====
The thread about namespaces still has me pondering a little.
I'm not sure it's relevant to my issue, though, is it?
I need to *instantiate* an object in order to assign it to an attribute
on a target object. So I do need to know the name of a concrete class to
instantiate. There's no "duck typing" there, is there?
====
Thanks Ryan, Chad, Austin, Richard, James, David, Christoph (phew!)
As said in the intro, all comments/corrections welcome!
Regards,
Simon