mateuszlewko
mateuszlewko

Reputation: 1129

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

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?

Upvotes: 6

Views: 1444

Answers (2)

kvb
kvb

Reputation: 55184

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.

Upvotes: 7

Tomas Petricek
Tomas Petricek

Reputation: 243041

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<_>

Upvotes: 6

Related Questions