phobos icon indicating copy to clipboard operation
phobos copied to clipboard

Date + Duration is not commutative

Open vpanteleev-sym opened this issue 7 months ago • 5 comments

import core.time;
import std.datetime;

void main()
{
	auto a = Date.init + Duration.init; // OK
	auto b = Duration.init + Date.init; // Error
}

Even though the order looks odd, I think it's more important to make sure addition is commutative for any types if we're going to overload it.

Same with other types that support addition with a Duration.

vpanteleev-sym avatar May 19 '25 12:05 vpanteleev-sym

Even though the order looks odd, I think it's more important to make sure addition is commutative for any types if we're going to overload it.

Why ? There are types where addition is not commutative.

Geod24 avatar May 19 '25 14:05 Geod24

There are types where addition is not commutative.

What are they?

vpanteleev-sym avatar May 19 '25 14:05 vpanteleev-sym

Anything where the offset is not of the same type as the thing being offset, depending on how the developer represent it. They can also be of the same type but with different values. For example:

struct MonetaryAmount { /* ... */ }
MonetaryAmount GBP (ulong value) { /* ... */ }
MonetaryAmount EUR (ulong value) { /* ... */ }
void main () {
    auto c1 = 50.GBP;
    auto c2 = 50.EUR;
    auto res = c1 + c2;
}

Here, a sensible implementation for addition could:

  1. Error out on different currencies;
  2. Use a common currency that makes sense in the problem domain (e.g. USD);
  3. Use the left-hand-side currency;

If the developer go with (3) the addition is non-commutative. Now after doing a bit more research there are less types than I expected - and for some types, they are non-associative but commutative (e.g. floats).

Now personally, it doesn't make much sense to me to add date to duration - but perhaps the convenience here is worth it ?

Geod24 avatar May 19 '25 15:05 Geod24

Use the left-hand-side currency;

Hmm, I see what you mean, though I would say that still counts as commutative. Even though the type is different, it is conceptually the same value. I find this example more compelling:

auto distance1 = 3.meters + 5.feet; // 4.524 meters
auto distance2 = 5.feet + 3.meters; // 14.84 feet

The units library could convert the right operand to the left one's type, but no matter the order and resulting type, it would be conceptually the same value.

and for some types, they are non-associative but commutative (e.g. floats).

On the other hand, this seems something different. Float addition is conceptually arithmetic addition, which is both associative and commutative, however this isn't reflected in the particular representation (and operator implementation) of the numbers.

Neither are quite like the case with Date and Duration. This particular case seems similar to writing -2 + 5 instead of 5 - 2, which is awkward but valid.

Either way, I don't have a big argument for why this should work beyond mathematical consistency.

vpanteleev-sym avatar May 19 '25 18:05 vpanteleev-sym

IMO the most directly-analogous example here is pointer + integer addition, which works in both directions:

int* p;
size_t i;
assert(p + i == i + p);

pbackus avatar May 19 '25 18:05 pbackus