Add type constraint to derived type F# (This code is not sufficiently generic)
Asked Answered
B

2

6

I have this interface:

type IMovingFunc< 'T > =
    abstract member Add : 'T -> unit

Now I want to create a generic type that implements Add function and uses (+) operator:

type MovingSum< ^T >(initial : ^T) =
    let mutable sum = initial

    interface IMovingFunc< ^T> with
        member inline this.Add x = sum <- sum + x

Unfortunately, I'm getting this error:

This code is not sufficiently generic. The type variable ^T when ^T : (static member ( + ) : ^T * ^T -> ^T) could not be generalized because it would escape its scope.

I've tried adding type constraint to MovingSum but it didn't help:

type MovingSum< ^T when ^T : (static member ( + ) : ^T * ^T -> ^T)>(initial : ^T) 

Could you please tell me how to fix this or is there other way to achieve this?

Boaten answered 27/11, 2016 at 14:41 Comment(2)
Types can't have type parameters with statically resolved constraints. Only functions.Towering
@FyodorSoikin That's not actually correct, although it might be better if it were...Wisnicki
L
6

As mentioned by Fyodor in the comments, types cannot have statically resolved constraints in the same way as functions (mainly because static constraints are handled using inlining - and you cannot really inline a whole type).

One way to address this is to make the constraint explicit in the type and then create a function with static member constraint that captures the functionality and passes it to the type.

In your example, you need the + operator, so we can add adder as a parameter of the type:

type MovingSum<'T>(initial:'T, adder:'T -> 'T -> 'T) =
    let mutable sum = initial
    interface IMovingFunc<'T> with
        member this.Add x = sum <- adder sum x

This does not require static member constraints, but you need to provide the extra parameter when creating MovingSum. This is not too bad, but you can avoid that by defining an inline function that creates MovingSum and captures the + operator:

let inline movingSum initial = 
  MovingSum(initial, fun a b -> a + b) :> IMovingFunc<_>
Lascar answered 27/11, 2016 at 15:3 Comment(0)
W
7

Tomas's answer is good. However, for completeness: it is possible to use statically resolved type parameters on types:

type MovingSum< ^T when ^T : (static member (+) : ^T * ^T -> ^T)>(initial : ^T) =
    let mutable sum = initial

    member inline this.Add(x:^T) = sum <- sum + x

let m = MovingSum(1) in m.Add(3) // works!

But note that:

  1. Member constraints need to be included where the parameter is declared.
  2. I get a few spurious warnings here, indicating that maybe this language feature is not fully-baked...
  3. This doesn't help you anyway, since interface members can't be inlined - given a value of an interface type, the compiler doesn't have any way to determine the runtime type that implements the interface, and so it's impossible to know what implementation to inline.
Wisnicki answered 27/11, 2016 at 16:55 Comment(0)
L
6

As mentioned by Fyodor in the comments, types cannot have statically resolved constraints in the same way as functions (mainly because static constraints are handled using inlining - and you cannot really inline a whole type).

One way to address this is to make the constraint explicit in the type and then create a function with static member constraint that captures the functionality and passes it to the type.

In your example, you need the + operator, so we can add adder as a parameter of the type:

type MovingSum<'T>(initial:'T, adder:'T -> 'T -> 'T) =
    let mutable sum = initial
    interface IMovingFunc<'T> with
        member this.Add x = sum <- adder sum x

This does not require static member constraints, but you need to provide the extra parameter when creating MovingSum. This is not too bad, but you can avoid that by defining an inline function that creates MovingSum and captures the + operator:

let inline movingSum initial = 
  MovingSum(initial, fun a b -> a + b) :> IMovingFunc<_>
Lascar answered 27/11, 2016 at 15:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.