dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.87k stars 783 forks source link

Generic constraint `comparison` is required despite type being restricted to `INumber` (generic math .NET 7) #14338

Open gurustron opened 1 year ago

gurustron commented 1 year ago

Repro steps

let inline test<'T when 'T :> INumber<'T>> x:'T = 
    if x > 'T.Zero then x
    else 'T.Zero

Actual behavior

Compilation error:

Severity Code Description Project File Line Suppression State Error FS0193 A type parameter is missing a constraint 'when 'T: comparison' FSharpTests Program.fs 37 Active

Expected behavior

Compiles like C# counterpart:

public T Tets<T>(T x) where T : INumber<T> => x > T.Zero ? x : T.Zero;

Known workarounds

Add the constraint:

let inline test<'T when 'T :> INumber<'T> and 'T:comparison> x:'T = 
    if x > 'T.Zero then x
    else 'T.Zero

Related information

charlesroddie commented 1 year ago

This is a consequence of https://github.com/fsharp/fslang-suggestions/issues/816 . FSharp equality and comparison badly need to move into the generic era.

dsyme commented 1 year ago

This is by design - F# structural equality and comparison builds on IComparable, not IComparable<T>

If you want to enforce non-structural comparison throughout your code you can do this:

open FSharp.Core.Operators.NonStructuralComparison
open System.Numerics

let inline test<'T when 'T :> INumber<'T>> (x:'T) = 
    if x > 'T.Zero then x
    else 'T.Zero

However this means, for example, you can't use structural equality on F# lists or tuples or arrays.

If you need to mix of structural comparison and non-structural comparison, you can do this:


let inline (>.) x y = FSharp.Core.Operators.NonStructuralComparison.(>) x y
let inline (>=.) x y = FSharp.Core.Operators.NonStructuralComparison.(>=) x y
let inline (<.) x y = FSharp.Core.Operators.NonStructuralComparison.(<) x y
let inline (<=.) x y = FSharp.Core.Operators.NonStructuralComparison.(<=) x y
let inline (=.) x y = FSharp.Core.Operators.NonStructuralComparison.(=) x y
let inline (<>.) x y = FSharp.Core.Operators.NonStructuralComparison.(<>) x y

open System.Numerics

let inline test<'T when 'T :> INumber<'T>> (x:'T) = 
    if x >. 'T.Zero then x
    else 'T.Zero
charlesroddie commented 1 year ago

That is interesting. How does that work? These operators don't work with IComparable<'T>.

open System
open NonStructuralComparison
type T() =
    interface IComparable<T> with member _.CompareTo(_:T) = 0
let compared = T() < T() // The type 'T' does not support the operator '<'

This one works on IComparable<'T> including INumber.

let inline (>.) (x: 'T) (y: 'T) = (x:> IComparable<'T>).CompareTo(y) > 0