dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.05k stars 4.69k forks source link

TimeSpan has all of the semantics of numbers, but none of the new interfaces #76225

Open davhdavh opened 2 years ago

davhdavh commented 2 years ago

Background and motivation

TimeSpan is fundamentally a long, and has most of the semantics of being a long. Yet, when adding the IMinMaxValue<T>, INumberBase<T> and so on, none of them were applied to TimeSpan. This prevents making things like Math.Min (if it had an overload that used the new interfaces) work on TimeSpans.

API Proposal

namespace System;

public class TimeSpan
        : IComparable,
          IComparable<TimeSpan>,
          IEquatable<TimeSpan>,
          ISpanFormattable,
          ISpanParsable<TimeSpan>,
//NEW
          IMinMaxValue<TimeSpan>,
          IAdditionOperators<TimeSpan, TimeSpan, TimeSpan>,
          IAdditiveIdentity<TimeSpan, TimeSpan>,
          IEqualityOperators<TimeSpan, TimeSpan, bool>,
          ISubtractionOperators<TimeSpan, TimeSpan, TimeSpan>,
          IUnaryPlusOperators<TimeSpan, TimeSpan>,
          IUnaryNegationOperators<TimeSpan, TimeSpan>,
          ISignedNumber<TimeSpan> //<-- problem with this, see below
{
...
}

API Usage

var min = Math.Min(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(1));

Alternative Designs

No response

Risks

A few semantics of the new interfaces does not really make sense for TimeSpan, e.g. TimeSpan can be negative, but the concept of NegativeOne does not really make much sense outside being a unary operator. and for some reason ISignedNumber<T> assumes the entire INumberBase<T> is applicable.

dotnet-issue-labeler[bot] commented 2 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

hez2010 commented 2 years ago

IMO all types having operator overloads should implement their corresponding operator interfaces. If a type is not number, it only isn't necessary to implement the INumberBase/INumber interface.

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation TimeSpan is fundamentally a long, and has most of the semantics of being a long. Yet, when adding the `IMinMaxValue`, `INumberBase` and so on, none of them were applied to TimeSpan. This prevents making things like Math.Min (if it had an overload that used the new interfaces) work on TimeSpans. ### API Proposal ```csharp namespace System; public class TimeSpan : IComparable, IComparable, IEquatable, ISpanFormattable, ISpanParsable, //NEW IMinMaxValue, IAdditionOperators, IAdditiveIdentity, IEqualityOperators, ISubtractionOperators, IUnaryPlusOperators, IUnaryNegationOperators, ISignedNumber //<-- problem with this, see below { ... } ``` ### API Usage ```csharp var min = Math.Min(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(1)); ``` ### Alternative Designs _No response_ ### Risks A few semantics of the new interfaces does not really make sense for TimeSpan, e.g. TimeSpan _can_ be negative, but the concept of NegativeOne does not really make much sense outside being a unary operator. and for some reason `ISignedNumber` assumes the entire `INumberBase` is applicable.
Author: davhdavh
Assignees: -
Labels: `api-suggestion`, `area-System.Numerics`, `untriaged`
Milestone: -
huoyaoyuan commented 2 years ago

The interfaces was implemented in the first version, but then removed when they were moved into System.Numerics namespace.

tannergooding commented 2 years ago

As indicated above, the original design had types like TimeSpan implementing such interfaces. However, API review decided we shouldn't expose that without more concrete scenarios showing the usage scenarios. That likely includes indicating how such APIs would be versioned.

and for some reason ISignedNumber assumes the entire INumberBase is applicable.

The interface name implies that it is a number like type and not just some arbitrary type that has a "sign".

hrrrrustic commented 2 years ago

more concrete scenarios showing the usage scenarios

For https://github.com/dotnet/runtime/issues/69590 would be nice to have IComparisonOperators<,> on TimeSpan, DateTime, etc

tannergooding commented 2 years ago

For comparison, IComparable<T> and IEquatable<T> is often a better choice due to the semantics and guarantees it provides (they are not the same as IComparisonOperators and IEqualityOperators, and the functions may return different results -- and do in the case of floating-point).

gusty commented 2 years ago

I think TimeSpan shouldn't implement IAdditionOperators<_,_,_> as it's loosely tied to numerics and for instance there is no concept of Unchecked Addition.

Ideally it should implement something more generic like IMonoid<_> and ISemigroup<_>, we definitely need those interfaces which are not tied to numerics. Other non numeric types like String, List<'T> can profit from these interfaces as well, this will allow to generalize over those operations which have some compelling use cases like accumulative error collection to name the first one that comes to my mind from my day to day work,

Then ideally IAdditionOperators<'t,'t,'t> should inherit from ISemiGroup<'t> if we had a kind of selective inheritance mechanism based on generics specializations, I wish we had it.

Regarding IMinMaxValue I think it makes sense to TimeSpan.

tannergooding commented 2 years ago

Ideally it should implement something more generic like IMonoid<_> and ISemigroup<_>, we definitely need those interfaces which are not tied to numerics.

Concepts like monoids and semigroups are incredibly difficult to expose. Some systems have tried to define or expose such types/support, but they are only loosely related to the actual mathematical term. This is in the same way that tensors, vectors, scalars, functions, and other terms are most often only very loosely related to the same mathematical concepts.

Such concepts exist in a purely mathematical sense where you have a theoretical "turing machine" with infinite time/resources. However, in the practical sense no such computer exists. Applications generally run on an imperative machine where such machine has finite resources and finite time. Data is broken up into smaller pieces for easy interaction. Interacting with data can often have side effects and it can imply introduced error in the form of rounding, wrapping, saturating, truncating, or more.

Such systems are typically minimally abstracted over with a basic type system. While the complexity of some type systems can vary, they all have limitations that further restrict what models can be exposed and how easy vs complex it is for others to consume the expose interface.

In the case of semigroup, an extremely simplified view is that it implies an associative binary operator. However, in computer programming (a + b) + c and a + (b + c) can differ in result. While it minimally holds true for certain types of primitive integers (namely those that are two's complement and provide "truncating" arithmetic), it falls apart for types which saturate, types which round, types which have side effects, etc. Many of these concepts (associative, commutative, distributive, identities, inverses, etc) only exist or hold true part of the time and even then often can't be represented via purely static data.

Because of this, among other reasons, we have no plans to add such interfaces now or in the future. It is not the goal or aim of the BCL to try and provide mathematical abstractions like this. The interfaces we expose take into account the practical limitations and interaction scenarios a developer requires. We also take into account that things like monoids are a higher level concept that many developers will never have been introduced to and may represent a very high entrance barrier as compared to a concept like IAdditionOperators<...>.

for instance there is no concept of Unchecked Addition.

Such a concept would make sense for TimeSpan, however. In general, anything that has a Min/MaxValue has some concept of "overflow" and therefore what happens when those boundaries are "passed". In many cases users will want/expect such overflow to fault (raise an exception/surface an error). However, there are likewise cases where another behavior is desirable instead. Today, TimeSpan always overflows but allowing for explicit saturation or wrapping would also be viable and potentially something worth exposing.

gusty commented 2 years ago

Concepts like monoids and semigroups are incredibly difficult to expose.

Well, the solution is easy, let's pick a different name, and don't enforce strict adherence to those properties. What about ITypeThatSupportsABinaryOperationWhichDoesntChangeItsType ? but please don't put it in Numerics.

Some systems have tried to define or expose such types/support, but they are only loosely related to the actual mathematical term

I'm totally fine with that, as that's not the goal. What I propose is not to re-create the abstract mathematical properties, but the practical ones, I mean something that encompass string, list<'t> and perhaps Timespan as well.

tannergooding commented 2 years ago

Well, the solution is easy, let's pick a different name, and don't enforce strict adherence to those properties. What about ITypeThatSupportsABinaryOperationWhichDoesntChangeItsType ?

You can just use IAdditionOperators<T, T, T>. Having to specify the inputs/outputs isn't really problematic. It allows maximum extensibility while requiring minimal additional typing.

but please don't put it in Numerics.

There is no reason why these interfaces can't be used elsewhere. They ultimately are very numeric oriented and are hidden away here because direct usage isn't as common. It's primarily a consideration for library/type authors rather than for application authors.

API review already discussed and considered this when deciding to put these interfaces in System.Numerics

I'm totally fine with that, as that's not the goal. What I propose is not to re-create the abstract mathematical properties, but the practical ones, I mean something that encompass string, list<'t> and perhaps Timespan as well.

You're going to need to provide some more concrete examples of what you think is missing and usage scenarios around how you expect such an abstraction to be used/be beneficial.

In the case of string/list/array, most of the functionality is covered by interfaces in System.Collections and System.Collections.Generic.

JochemPalmsens commented 1 year ago

Maybe also add IDivisionOperators<TimeSpan,double,TimeSpan> and IDivisionOperators<TimeSpan,TimeSpan,double>, etc? To expose more of the mathematical operations?

would improve usability in constrained generic methods. I do understand @gusty 's concern. However, the current implementation is just too limited, and requires overloaded methods that contain duplicate code (= not very clean)

ghost commented 10 months ago

Tagging subscribers to this area: @dotnet/area-system-datetime See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation TimeSpan is fundamentally a long, and has most of the semantics of being a long. Yet, when adding the `IMinMaxValue`, `INumberBase` and so on, none of them were applied to TimeSpan. This prevents making things like Math.Min (if it had an overload that used the new interfaces) work on TimeSpans. ### API Proposal ```csharp namespace System; public class TimeSpan : IComparable, IComparable, IEquatable, ISpanFormattable, ISpanParsable, //NEW IMinMaxValue, IAdditionOperators, IAdditiveIdentity, IEqualityOperators, ISubtractionOperators, IUnaryPlusOperators, IUnaryNegationOperators, ISignedNumber //<-- problem with this, see below { ... } ``` ### API Usage ```csharp var min = Math.Min(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(1)); ``` ### Alternative Designs _No response_ ### Risks A few semantics of the new interfaces does not really make sense for TimeSpan, e.g. TimeSpan _can_ be negative, but the concept of NegativeOne does not really make much sense outside being a unary operator. and for some reason `ISignedNumber` assumes the entire `INumberBase` is applicable.
Author: davhdavh
Assignees: -
Labels: `api-suggestion`, `untriaged`, `area-System.DateTime`
Milestone: -
roji commented 4 days ago

FYI NodaTime is adding generic math interfaces to their types (https://github.com/nodatime/nodatime/issues/1693).