"Here's a Dog."
"Hi, Dog!"
"It licks your face, chews your shoes, retrieves things, wags its tail, and loves you 'til death do you part."
"Great! Just what I want!"
"It also rides a unicycle."
"Huh?"
"But first you'll want it to sing
Pagliacci."
"WTF?"
...
"Here's a Numeric."
"Hi, Numeric!"
"It adds, gets compared with other Numerics, gets raised to a power, and you can get its absolute value and mask its bits."
"Great! Just what I want!"
"It also performs the ago operation."
"Huh?"
"But first you'll want it to perform the days operation."
"WTF?"
...
[More later about confused types, with or without
MonkeyPatching, as discovered in
RubyOnRails.]
...
"Come on, Fido! Sing
Pagliacci!"
"Oooo, ooo oo oooo oooo. Oo oo oo oo oo oooo oooo."
"Atta boy! Good Dog!
Good Dog! Go on, now! Get your unicycle! Get your unicycle!"
The Numeric#days method in
RubyOnRails takes the Numeric, multiplies it by the number of seconds in a typical day (ignoring the possibility of
DaylightSavingTime shifts and rare
LeapSeconds), and returns a Numeric. The Numeric#ago method gets the number of seconds from the beginning of the Unix era until the current time, subtracts the Numeric from it, and returns a Numeric.
Hence, 5.days is valid
RubyLanguage and it's the number of seconds in 5 typical days. And 5.days.ago is valid Ruby and it's the number of seconds from the beginning of the era until 5 "days" before the current time. (There exists the possibility that this calculation is incorrect on account of politicians arbitrary shifting our clocks and calendars).
What I find disturbing is, 5.ago is perfectly valid Ruby (it's the number of seconds from the beginning of the era until 5 seconds before the current time), but it's invalid to the human brain. So is 5.ago.days (a truly large number of seconds != 5.days.ago). So is 5.days.days.days >> 3. So is 17 | 5.days. And, surprisingly, from a "
DomainSpecificLanguage" perspective, 17.days.ago.upto 5.days.ago loops a very large number of times, once for each second in 12 days.
"Oh," you (whoever you may be) say, "but the
UnitTests will catch these errors!" Really? When you toss extra methods into a class that never anticipated your "enhancements," what makes you think there's any such thing as a "unit" and how do you come up with test
EquivalenceClasses? You know as well as I do that you can't test every permutation and combination of methods and values. So how do you know what tests to write to ensure that 5.days passes, 5.days.ago passes, 5.ago fails, 5.ago.days fails, 5.days.days.days >> 3 fails, and 17 | 5.days fails?
"Oh," you say, "but no one will write 5.days.days.days >> 3 because anyone with eyeballs can see from the
DomainSpecificLanguage that that's nonsense!" So now you're relying on eyeballs, as if every project under the sun uses
CodeReviews and/or
PairProgramming and/or enough of
EricRaymond's
OpenSource eyeballs. I think you've just admitted to the inherent untestability of Ruby code. Welcome to the club.
See also
ExtraLegsOntoAdog CuteProgramming