fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
344 stars 21 forks source link

Checked arithmetic operators #1035

Open Happypig375 opened 3 years ago

Happypig375 commented 3 years ago

Checked arithmetic operators

The C# team is considering to add checked operators: static T checked operator +(T lhs, T rhs); which will be preferred in checked contexts, and only consider the regular (infer checkedness from compiler switch) operator if the signature of the checked operator does not fit. In contrast, when not in a checked context, checked operators will be ignored.

Although F# reserves checked as a keyword, my guess is that as with event and sealed, attributes will be used.

open System
type C =
    { c : int }
    static member (+) ({ c = x }, { c = y }) = x + y
    [<Checked>]
    static member (+) ({ c = x }, { c = y }) = Checked.(+) x y

With #96:

open System
type C =
    { c : int }
    static member (+) ({ c = x }, { c = y }) = x + y
    [<Checked>]
    static member (+) ({ c = x }, { c = y }) =
        open Checked
        x + y

This will emit op_Addition and op_AdditionChecked. If a checked operator is defined without the regular (infer checkedness from compiler switch) equivalent, an error will be raised.

C# will imply a checked scope inside implementations of checked arithmetic operators. Not sure if F# should follow.

The existing way of approaching this problem in F# is to write op_AdditionChecked directly that can only be used in C# once implemented in their end but being forced to write op_AdditionChecked when using from F#.

Whether allowing explicit unchecked operators is still unresolved from the C# side.

Pros and Cons

The advantages of making this adjustment to F# are

There is no way for a user to declare a type and support both checked and unchecked versions of an operator. This will make it hard to port various algorithms to use the proposed generic math interfaces exposed by the libraries team. Likewise, this makes it impossible to expose a type such as Int128 or UInt128 without the language simultaneously shipping its own support to avoid breaking changes.

The disadvantages of making this adjustment to F# are

This adds additional complexity to the language and allows users to introduce more kinds of breaking changes to their types.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

Related suggestions: https://github.com/dotnet/csharplang/issues/4665 - the linked C# issue

Affidavit (please submit!)

Please tick this by placing a cross in the box:

Please tick all that apply:

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

charlesroddie commented 3 years ago

Suggest closing to reduce the number of open issues in fslang-suggestions and reopening when the C# feature this is dependent on is confirmed.

Happypig375 commented 3 years ago

Oh obviously #179 should be closed too. It must await C# alignment.

Happypig375 commented 3 years ago

@charlesroddie And as you can see, the number of open issues is not a concern. https://github.com/fsharp/fslang-suggestions/issues/807#event-4892372969 https://github.com/fsharp/fslang-suggestions/issues/610#event-4892374548 https://github.com/fsharp/fslang-suggestions/issues/608#event-4892377127 https://github.com/fsharp/fslang-suggestions/issues/875#event-4892380966

dsyme commented 3 years ago

I'm happy to have all these issues being tracked. It's good to go over things with a fine toothcomb :)

dsyme commented 2 years ago

I'd like to make progress on this issue now the dust is settling on .NET 7

dsyme commented 2 years ago

For definition of these operators, @Happypig375's suggestion of using an attribute is reasonable.

open System
type C =
    { c : int }
    static member (+) ({ c = x }, { c = y }) = x + y
    [<Checked>]
    static member (+) ({ c = x }, { c = y }) = Checked.(+) x y

Or we could ask people to use the name:

open System
type C =
    { c : int }
    static member (+) ({ c = x }, { c = y }) = x + y
    static member op_CheckedAddition ({ c = x }, { c = y }) = Checked.(+) x y

I'm actually not too fussed about asking people to use the explicit form.

There is a seperate difficult question about whether the definition of Checked.(+) is modified so that it uses a special SRTP constraint where op_CheckedAddition is used if it is present on a type, and op_Addition otherwise (this is for user-defined types).

Currently this is not done and it would technically be a breaking change to start doing it. However arguably it would always be the "right" thing to do and there are almost no types with op_CheckedAddition in existence today. Given the relatively low use of open Checked in F# code I think this is the right thing to do, under a language version switch