Design, Notation and Relative Utility: A Matter of Perspective
Very early Friday morning (2006-01-06,) I released a new version of Chronos and announced the new version on the Chronos blog--including an explanation of the reason the new version was necessary, wherein I said the following:
"So I have reluctantly changed WeekOfMonthDayOfWeek so that it can gracefully handle weekly periods that overflow the number of days in a month, even though that's contrary to my preferred philosophy."
Ever since that post, I have found myself reconsidering whether the change I made to WeekOfMonthDayOfWeek actually is against my design philosophy--and if it is, whether or not my design philosophy is wrong in this case.
I think a discussion of the relevant issues involved in this matter, and the conclusions to which I have come, may be of wider interest. Hence this post.
As currently implemented, instances of the WeekOfMonthDayOfWeek class represent a partial date of a particular type in a particular way. The type of partial date represented by a WeekOfMonthDayOfWeek instance is what I call an AnnualDate, which is a date that does not specify any particular year. The way that a WeekOfMonthDayOfWeek instance specifies an AnnualDate is as the first occurrence of a specified day-of-the-week on or after a particular day of a particular month.
For example, evaluating "WeekOfMonthDayOfWeek month: 11 minDayOfMonth: 22 dayOfWeek: 5" creates an instance of WeekOfMonthDayOfWeek that represents the first occurrence of a Thursday on or after 22 November (which, by the way, is the rule for Thanksgiving Day in the US.) "The first Thursday on or after the 22nd" is equivalent to "the fourth Thursday of the month."
More generally, the first Thursday on or after the 1st of a month is equivalent to the first Thursday of the month, the first Thursday on or after the 8th of the month is equivalent to the second Thursday of the month, the first Thursday on or after the 15th of the month is equivalent to the third Thursday of the month, and the first Thursday on or after the 22nd of the month is equivalent to the fourth Thursday of the month. In the case of a month with 30 days (such as November,) the first Thursday on or after the 24th of the month is equivalent to the last Thursday of the month.
However, the current implementation of WeekOfMonthDayOfWeek is more general than it was as I originally implemented it. Originally, it could represent dates as the Nth specified day-of-week of the specified month, or else as the last specified day-of-week of the specified month, but could not represent "the first specified day-of-week on or after a specified day-of-month of a specified month" (as it does now.)
I made the implementation of WeekOfMonthDayOfWeek more general because such was necessary in order to handle the generality with which time zone transition rules are specified by the Olson Time Zone Database. However, in spite of this more generalized reimplementation, I never changed my conceptual model of WeekOfMonthDayOfWeek from what it was originally. And according to that conceptual model, a WeekOfMonthDayOfWeek should only represent dates within the month its rule specifies, and not dates in other months.
Of course, the first Friday on or after the 26th of March may be the first day of April--and April is obviously not March. And from the perspective of my original conceptual model of WeekOfMonthDayOfWeek, which was that a WeekOfMonthDayOfWeek should specify a date within a specified month as a function of a specified day-of-week and specified week-of-the-month, such a specification of a date in some other month is improper.
However, it should be noted that WeekOfMonthDayOfWeek has always included an offset number of days from the nominally specified date. By default, the offset is zero days. However, a WeekOfMonthDayOfWeek whose offset is set to +3 days represents the date 3 days later than (for example) "the last Sunday of the specified month." The offset may have the effect of making a WeekOfMonthDayOfWeek instance specify a date not in the same month as the one specified in its rule. So even before I generalized the implementation, it was possible to create an instance of WeekOfMonthDayOfWeek that specified a date in a month other than the one specified in the instance's rule.
A very analogous issue must be confronted when implementing any date creation message, such as "DateAndTime class>>year:month:day:". Should "DateAndTime year: 2006 month: 2 day: 29" be treated as an error, or should it instead be interpreted as the date 29 days later than 2006-01-31--in other words, as 2006-03-01?
The issue can be generalized as follows: Should instance creation methods (or even more generally, mutational methods) treat as errors any parameter sequences that would not be legal/valid according to the notational rules for representing a value of that type, or is it acceptable for such methods to interpret their input parameters according to a more general, mathematical model--a representational model different than the standard one? Is the fact that there is no such date as 2006-02-29 sufficient reason, in and of itself, to forcibly prevent the creation of a DateAndTime by using a parameter sequence that is interpreted to be a reference to the date 29 days later than 2006-01-31?
Consider the Smalltalk expression "6/8." If one prints this value, the text that will be printed is "3/4." This establishes the precedent that the canonical notation to represent a value need not be the same as the parameters used to create the value. If it's considered kosher to autoconvert "6/8" to "3/4," what's wrong with converting "DateAndTime year: 2006 month: 2 day: 29" to 2006-03-01?
One objection would be that "6/8" has a well-defined meaning, and is legal acording to the notational rules of arithmetic, whereas "2006-02-29" is either a date that does not exist, or else is not a formally-defined notational synonym for "2006-03-01." But this objection rests on two assumptions that may be untrue. Specifically, the objection assumes that the same constraints that apply to the standard notational representation should also apply to the arguments accepted by an instance creation and/or mutational message, and it assumes that there can only ever be one canonical notational scheme for representing the values of a type, and that there will never be any need or justification for alternatives.
A better objection would be that it is far more likely that a "non-canonical" date designation is an error than it is that anyone would actually need and/or want to specify a date using a notational scheme such as "799 days after subtracting 200 months from 2000-01-01" (i.e., "DateAndTime year: 2000 month: -200 day: 800.") This objection appears to me to be quite valid. Apparently, the ANSI Smalltalk Committee came to the same conclusion, because the ANSI Smalltalk Standard requires that an Exception be raised in such a case--which happens to be what Chronos does.
But there is a problem with the "no one is likely to need or intentionally use it" objection: it applies well enough to the specific case we have been discussing, but it may not apply to all (or even most) other cases. In fact, the proper generalization of the objection would be to apply a "relative utility" test to each case. In other words, we should consider the relative utility of whatever alternative (and perhaps more general) representation scheme might be supported by any instance creation and/or mutational methods versus the relative utility of detecting any unintended errors in notation.
So now we return to the case which started this dicussion.
Firstly, we note that there is no canonical, standard or well-known notational scheme for representing "the first specified day-of-week on or after the specified day-of-month of the specified month."
Secondly, we note that there is no compelling reason to require that the date specified by a WeekOfMonthDayOfWeek instance be in the same month as the month referenced by its rule. It's true that it may be a tad surprising when the specified date happens to fall in a different month, but whether or not one is surprised depends upon how one views the function and purpose of a WeekOfMonthDayOfWeek instance. It's a matter of perspective.
Thirdly, we note that there are compelling reasons to allow the date specified by a WeekOfMonthDayOfWeek instance to fall in a month other than the one specified in its rule. One is that the Olson Time Zone Database apparently specifies such annually-recurring dates using that level of generality. Another is that "the first Sunday on or after the 31st of May" is a perfectly well-defined concept, and could in fact be an example of a rule that might be adopted in some law, regulation or policy promulgated by some legislature, agency or other organization.
In view of the foregoing, I have decided that what was wrong wasn't my design philosophy, but rather my mental model of the function and purpose of WeekOfMonthDayOfWeek. Or to put it another way, my new conceptualization of WeekOfMonthDayOfWeek has greater relative utility than my old one.
I have, however, clarified and refined my design philosophy as a result of having thought through these issues. I hope that those who have read this article will derive some benefit from having considered these matters along with me.
No comments:
Post a Comment