Categories

Posts in this category

Wed, 17 Nov 2010

The Real World Strikes Back - or why you shouldn't forbid stuff just because you think it's wrong


Permanent link

tl;dr;; version: arbitrary API limitations do more harm than good, even if meant well in the first place.

Most advanced libraries that help you with date calculations have separate data types for a point in time, and a time span. That's because those two concepts actually have different semantics. It doesn't make sense to add two points in time, but it does make sense to add two durations, or add a duration to a point in time.

In Perl 6, those two types are called Instant and Duration. And obviously it makes sense to multiply a Duration with a number, but it doesn't make sense to multiply two Durations, or take a power of a Duration. Right?

That's the opinion that both the Perl 6 specification and the implementation in Rakudo reflected. Until recently, when somebody started to actually use it.

That's when the real world struck back. Carl Mäsak did some timings, and then calculated averages and standard deviations. And for calculating standard deviations, you actually have to square those durations, add them up, and then calculate the square root.

So this perfectly legitimate use case shows that multiplication (and also exponentiation) are perfectly fine operations on Durations. Likewise the current specification disallows division of two Durations. Why? It's perfectly fine to ask for the ratio of two time spans. How much longer (or shorter) are my meetings with my current boss, compared to those with my previous boss? That's the question that Duration / Duration answers.

So, the real world taught me that putting restrictions on the allowed operations is a bad idea. It was meant well, it was supposed to catch operations that don't made sense to the designer, and presumably would catch some error that a confused beginner might make. But in the end it did more harm than good.

Currently the Duration class stores a number, and re-dispatches all operations to that number, forbidding some of them. Having learned my lesson, I suggest we get rid of it, and have Instant - Instant return that number straight away. If some day we want to add functionality to Duration, we can still create that class as a subclass of the number.

[/perl-6] Permanent link

Comments / Trackbacks:

Trackback URL: /blog-en/perl-6/real-world-strikes-back.trackback

Steve wrote


Hi.

I'm guessing that a duration object would cover a period of time between two instants - perhaps 7.75 hours - 120 seconds - three years - could all of these be considered durations?

If this is the case, It would appear to me that the solution Mäsak required wasn't to multiply two durations.

The real required is subtle - but different.

I would have thought that for the calculation, the magnitude of the duration would be required rather than just an arbitary period - the magnitude would vary for durations - and what is relevant for some purposes ( a nice round number like seconds ) may not be the same as for others.

I'm not sure the assumption is usefull for either advanced programmers or novices - I would encourage a novice to think, to have a small boundry to help orientate their mind to the problem space - it could be argued that a duration.seconds / duration.seconds ( as an example ) helps - it makes the loss of information explicit.

The final comment seems to define some of my fears -

If some day we want to add functionality to Duration, we
can still create that class as a subclass of the number.

Rather than regarding Duration as an entity in its own right, to be expanded and enriched, this remark seems to tie it forever to an underlying numeric implementation. I suspect this leaves the proposal open to an accusation of being a practice I've heard called "primitive obsession"?

dankuck wrote


It seems safe to start allowing it. Since legacy code doesn't yet use the disallowed operations, it will still run correctly. The only code that is in danger is code that depends on exceptions being thrown. That seems limited to test suites and methods that use disallowed operations for "type checking".

It's still frustrating though. You're right that it would've been less trouble to just allow these operations since they were technically possible.

Steve wrote

Having read S02
http://perlcabal.org/syn/S02.html

I'm not to sure of the benefit of a duration class as specified - and understand more of the frustrations - I consider Duration to be useful as a class level similar to Calendar objects - but the form suggested seems to be too low level ( in my narrow field of application programming experience ) to be of much use to me?

Songmaster wrote

Units are important
I agree with Steve, the units of the duration usually matter, so the user should be required to state whether they want the number they get back to be expressed in seconds, hours, days or years etc. Of course that opens the question as to whether they want just 123.45 seconds, or if they want to display the same duration as 2 minutes, 3 seconds, 450 milliseconds. That's an API issue, but I can see uses for both.

Eevee wrote


This is totally crazy to me. Squaring a duration is fine if you're going to actually preserve the units and have a whole unit system in the stdlib, but otherwise, you're pretending a physical quantity is just a number. If you want to square the number of seconds, you should ask for the number of seconds, not expect an object to act like one. Encapsulation and all that.

What's the square of an hour? 1? 3600? 12960000? 12960000000000? Please don't build this silly ambiguity into such a core part of the language. Perl 5's DateTime is ornery for good reason.

Dividing two durations makes perfect sense, of course; the units merely cancel.

Keith Thompson wrote

Then why not just use numbers?
If all numeric operations on Durations are going to be allowed, then why not just use numbers in the first place? What does making Duration a separate type buy you?

I suggest that squaring a Duration is a sufficiently unusual thing to do that it deserves to be made explicit. Presumably you can explicitly convert a Duration to its numeric value. (I like the "duration.seconds" syntax Steve mentioned; is that already there?) So do that: convert each Duration to a number, square the numbers, add them, take the square root, and convert the result back to Duration. And let the compiler warn you if you multiple two durations *unintentionally*.

(Either that or support more general units, so the square of a Duration is a number of square seconds.)

hobbs wrote


One more word of agreement with Steve and Eevee -- squaring or multiplying durations is *wrong* and broken unless you have a class that can represent units of Duration^2.

Example: (2 hours)^2 = 4 (hours^2). (120 minutes)^2 = 14400 (minutes^2). *If* you can store those values with their correct units, they're usable. But if you make the mistake of doing operations on the underlying numbers while leaving the units the same, giving 4 hours and 14400 minutes respectively, then what happens if you try to add them? 14400 minutes converts to *240 hours*, which is a completely useless quantity, giving a sum of 244 hours. Divide that by two to get 122 hours and take the square root (making again the same mistake with the units) and you get a bit over 11 hours. Congratulations, you just figured out that the RMS of 2 hours and 2 hours is 11 hours!

Now finally... there should be a Duration class. It should *not* represent a single number in any unit, but instead be a compound type like a DateTime::Duration in Perl 5. This is because the ideas of, e.g. "Add 1 day" and "Add 24 hours" aren't identical -- they differ across e.g. a DST change event. And such a Duration *shouldn't* support operations such as squaring or multiplication with another Duration because they're meaningless, but it *should* be able to produce a Numeric value representing its magnitude in some unit, and that number can be manipulated all you want.

Moritz wrote

Units
I don't understand all your horrors about units and squaring durations. The spec explicitly says that using a Duration as a number returns the value in seconds.

So things like (2 hours)**2 are well defined.

Perl 6 doesn't have a system for storing arbitrary units. Duration is the only type that we know the unit of. Now there are three possible solutions

1) disallow some operations that do make sense, just because we can
2) assume a default unit
3) implement a complete unit system

1) doesn't reallly makes sense, as I argued at length in the blog post. 3) is really outside the scope of a general purpose programming language. It's a nice addition as a user-space module (and Perl 6 is flexible enough to allow that), but adding it just bloats the language.


A final remark: Durations really store distances between points in time, not points in a calendar. They don't care about time zones, leap seconds or daylight saving time. They *can* be represented as seconds. And since that's the simplest solution, I don't see why they shouldn't.

Klausens wrote


I think the question is how many chars you save on the one hand and support bugs on the other hand.

In Perl6 it should be easy to force a numeric context for your Duration, so you can calculate with it. re-convert it to a Duration also should be not the problem.
If you regard this example as a special case, a manageable expense.

My fear is, that like in Perl5 (too?) many things are possible somehow, but if you want to work earnest you need:
use strict; use warnings; ... and then things become unhandy.
I still found no easy way to say a "eq" that its ok that a value is undef or undef equals undef. If Perl5 was designed from scratch to be stricter maybe there would be a nicer way.

hobbs wrote


A "Duration" that represents a number of seconds is a fundamentally useless type and should be replaced with Num -- or upgraded to be actually useful. :)

Steve wrote

Behavour not Units
"I don't understand all your horrors about units"

Firstly - I agree with the thrist of your article, and concur with the conclusion that "Having learned my lesson, I suggest we get rid of it, and have Instant - Instant return that number straight away"

However - the units thing scares me.

Any concern I have over units isn't about units - its about the risk of creation of Anemic Classes - http://www.martinfowler.com/bliki/AnemicDomainModel.html

The emphasis on units seems to present the assumption that the important part of representing certain concepts is with some arbitary label, rather than the underlying behavour which would be found in a class - Which to me seems to encourage bad design and sloppy programming.

As an example - in the field of "money" - a common concept in programming.

To share $3.55 4 ways is more difficult that just ( $3.55 / 4 ) - you might want three times 89 cents, and once times 88 cents.

For me, that deserves to be encapsulated, so as to keep code free of cruft, and ensure that we aren't accidentally manufacturing money.

Allowing arbitary types provides false confidence, and promotes sloppy programming - if its important enough to have a unit it often warrents its own class.

Write a comment

The comments on this blog post have been disabled; the comment form below will not work.

 
Name:
URL: [http://www.example.com/] (optional)
Title: (optional)
Comments:
Save my Name and URL/Email for next time