fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Using multiple operators in INumber-constrained generic functions leads to compiler error

Open En3Tho opened this issue 1 year ago • 12 comments

Please provide a succinct description of the issue.

// ok
let numericOpsPlus<'T when INumber<'T>>(num: 'T) =
    num + num

// FS0670 - code is not sufficiently generic
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    num + num + num

// ok again
let inline numericOpsPlusPlusInlined<'T when INumber<'T>>(num: 'T) =
    num + num + num

En3Tho avatar Apr 17 '24 16:04 En3Tho

Note: Works fine when chaining op_Addition calls.

vzarytovskii avatar Apr 17 '24 17:04 vzarytovskii

For reference — these tests should be extended to cover this:

https://github.com/dotnet/fsharp/blob/273cea532b81b33596d9635c6d8c6fb0a610093d/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/testFiles/UseSRTPFromIWSAMGenericCode.fs#L1-L33

brianrourkeboll avatar Apr 17 '24 17:04 brianrourkeboll

Note: Works fine when chaining op_Addition calls.

Yes, as in

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.(+) (num, num')
    |> fun num' -> 'T.(+) (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.op_Addition (num, num')
    |> fun num' -> 'T.op_Addition (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    let (+) x y = 'T.(+) (x, y)
    num + num + num

brianrourkeboll avatar Apr 17 '24 17:04 brianrourkeboll

Note: Works fine when chaining op_Addition calls.

Yes, as in

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.(+) (num, num')
    |> fun num' -> 'T.(+) (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    num
    |> fun num' -> 'T.op_Addition (num, num')
    |> fun num' -> 'T.op_Addition (num, num')

or

let numericOpsPlus<'T when INumber<'T>> (num: 'T) =
    let (+) x y = 'T.(+) (x, y)
    num + num + num

Yes, also:

open System.Numerics
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    ((num + num) : 'T) + num

and

open System.Numerics
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    'T.op_Addition('T.op_Addition(num, num), num)

vzarytovskii avatar Apr 17 '24 17:04 vzarytovskii

We suffer flexibility of the (+) here. I am not fully sure we can "fix" it here without seriously breaking something. I wonder if it's the same with some other operators (maybe not that common in F#).

vzarytovskii avatar Apr 17 '24 17:04 vzarytovskii

Yes, also:

open System.Numerics
let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =
    ((num + num) : 'T) + num

This might show where the problem is: something is probably getting confused when looking at the multiple type parameters in IAdditionOperators<'TSelf, 'TOther, 'TResult> and trying to unify the 'TResult of the first application with the 'TOther of the second.

brianrourkeboll avatar Apr 17 '24 17:04 brianrourkeboll

Yes, also:

open System.Numerics

let numericOpsPlusPlus<'T when INumber<'T>>(num: 'T) =

((num + num) : 'T) + num

This might show where the problem is: something is probably getting confused when looking at the multiple type parameters in IAdditionOperators<'TSelf, 'TOther, 'TResult> and trying to unify the 'TResult of the first application with the 'TOther of the second.

It's probably only in conjunction with + (might be others too).

vzarytovskii avatar Apr 17 '24 17:04 vzarytovskii

IAdditionOperators<'TSelf, 'TOther, 'TResult> and trying to unify the 'TResult of the first application with the 'TOther of the second.

@brianrourkeboll regarding the above, since it's constrained via INumber, I would expect it to have all typars to be unified.

vzarytovskii avatar Apr 17 '24 17:04 vzarytovskii

It's probably only in conjunction with + (might be others too).

It does for the other System.Numerics operators (-, *, %, unary + and -, etc.).

Interestingly, this does not trigger the problem:

open System.Numerics

#nowarn "3535"

type IFace<'a, 'b, 'c> =
    static abstract (>*..*<) : 'a * 'b -> 'c

let customOps<'T when IFace<'T, 'T, 'T>> (num: 'T) =
    num >*..*< num >*..*< num

type T =
    | T of int
    interface IFace<T, T, T> with
        static member (>*..*<) (T a, T b) = T (a + b)

customOps (T 1)

brianrourkeboll avatar Apr 17 '24 17:04 brianrourkeboll

It's probably only in conjunction with + (might be others too).

It does for the other System.Numerics operators (-, *, %, unary + and -, etc.).

Interestingly, this does not trigger the problem:


open System.Numerics



#nowarn "3535"



type IFace<'a, 'b, 'c> =

    static abstract (>*..*<) : 'a * 'b -> 'c



let customOps<'T when IFace<'T, 'T, 'T>> (num: 'T) =

    num >*..*< num >*..*< num



type T =

    | T of int

    interface IFace<T, T, T> with

        static member (>*..*<) (T a, T b) = T (a + b)



customOps (T 1)

Yeah, many of the "standard" operators have somewhat special treatment.

vzarytovskii avatar Apr 17 '24 17:04 vzarytovskii

By the way, this is how I found this bug: https://github.com/m4rs-mt/ILGPU/issues/463#issuecomment-2061598101

I've been playing with F# and ILGpu and so far it feels quite nice.

En3Tho avatar Apr 18 '24 04:04 En3Tho

When using inline, code does compile to

public static T numericOpsPlus<T>(T num) where T : INumber<T>
    {
        T val = num + num;
        return val + num;
    }

vzarytovskii avatar Apr 18 '24 08:04 vzarytovskii