
There's a half-fun-and-full-earnest proverb: It is better to have one data type with 100 functions than ten data types with 10 functions each. I have some guidelines that I wrote up for a class this year: http://www.cs.otago.ac.nz/cosc326/resources/data.htm. They were given to students who have been trained in OOP, and the example is in Java, but my perspective was value orientation. One aspect of value orientation that's *not* brought out in those notes but really should be is that value orientation goes hand in hand with thinking of the values as elements of some *ALGEBRA* and a determination that the values and operations should combine in a way that MAKES SENSE and is as easy as practical to reason about. Just today I was looking at a mere 3 pages of OOP code (and in fact if you threw away the comments and test cases there was under 1 page of code) which left me completely baffled. It's basically a zipper, with operations canGoBackward, canGoForward, goBackward, goForward, and goTo, except that goTo adds an element, and a dummy element, and backs up over the dummy element. But if you add three elements in succession with goTo, and back up once, you get the 2nd element, not the 3rd. Presumably in the original context it *does* whatever the author wanted, but I can't figure out what it *means* and certainly would not be able to use it myself. The thing that has me jumping around shouting and looking for someone to bite is that the author *knew* what the intended semantics of the operations was but didn't think it would matter to his readers... Let me offer an example from the ANSI Smalltalk standard. <blockquote> 5.7.8.10 Message: copyReplaceFrom: start to: stop withObject: replacementElement Synopsis Answer a new collection conforming to the same protocols as the receiver, in which the elements of the receiver between start and stop inclusive have been replaced with replacementElement. Definition: <sequenceReadableCollection> This message can be used to insert, append, or replace. There are three cases: 1. If stop = start - 1, and start is less than or equal to the size of the receiver, then replacementElement is inserted between the elements at index stop and start. None of the receiver’s elements are replaced. 2. If stop = the size of the receiver and start = stop + 1, then the operation is an append, and replacementElement is placed at the end of the new collection. 3. Otherwise, the operation is a replacement, and each of the receiver’s elements in the given range is replaced by replacementElement. The parameters start and stop must be non-negative. Collections that by definition enforce an ordering on their elements are permitted to refine this message to reorder the result. <snip/> </blockquote> Now, as object-oriented definitions go, there's not much wrong with this. Cases 1 and 2 are really the same case, so the description is more complicated than it needs to be, but that's about it. You can either replace 1 or more existing elements with instances of a new one, or insert exactly 1 element at any position. From a value perspective, it's clear that there are two operations here: seq copyReplaceFrom: start to: stop withObject: newItem -- replace existing elements seq copyAdd: newItem beforeIndex: start -- insert one new element They have different validity conditions: 1 <= start <= stop <= seq size for the first 0 <= start - 1 <= seq size for the second They have different effects on the size: seq size = result size for the first seq size + 1 = result size for the second One is idempotent: if seq1 = seq copyReplaceFrom: a to: z withObject: x and seq2 = seq1 copyReplaceFrom: a to: z withObject: x (under the first reading), then seq1 = seq2 but the other is not: the sizes being different. Oops. I just told a lie. The first reading is NOT idempotent if the receiver is a collection that keeps its elements in sorted order. So it _is_ idempotent for some receivers and it is _not_ idempotent for others. Again, the value perspective would be unhappy with this. I suppose you could summarise it as - the value perspective asks "what does this value MEAN?" and has as its characteristic activity trying to reason about correctness - the object perspective asks "what does this object DO?" and has as its characteristic activity using a debugger.