From: Austin Ziegler Date: 2003-11-06T12:27:48+09:00 Subject: Re: Managing metadata about attribute types On Thu, 6 Nov 2003 10:16:41 +0900, Simon Kitching wrote: > 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. It's not simply a matter of conversion from a String, as I'll demonstrate below. > Here's my original class, with the initial implicit assumptions > spelled out more clearly as comments. > > 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 Let's test that assumption. 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. 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.) In a statically typed language, conversions like this can be made implicit because the types themselves are explicit. The Java version *will always* be dealt with as if it were a float ... because it always *will* be a float. 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. > 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 disagree. If you want to treat the attribute as a float, then it's your responsibility to ensure that it *is* a float. Otherwise, you'll get unexpected results when someone doesn't *quite* respect the API/contract. [...] > Not to mention that writing those "conversion" methods by hand is > ugly. Well, they can be. That's why I wrote the extension that I did. >> 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. Well, yes, the documented contract is violated ... but there's no programmatic contract. IMO, defensive programming suggests that if you need something to behave a particular way, you do what you can to ensure it. >> 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. [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. > 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"... Maybe there's a place here for an enhanced version of #coerce. > Changing the StockItem class contract is one solution, but that > screws up all other code that really depended on the original > contract being valid. No, it doesn't. Doing a #to_f doesn't change the original contract. > 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. Why would you want to globally control it? The parsedate routine (don't quite remember where it sits) handles this. > The thread about namespaces still has me pondering a little. I'm > not sure it's relevant to my issue, though, is it? It's an offshoot of StrongTyping. When you do a #kind_of? test, you are doing something of a namespace test. -austin -- austin ziegler * austin@halostatue.ca * Toronto, ON, Canada software designer * pragmatic programmer * 2003.11.05 * 22.26.52