Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic interface where type-parameter supports addition

Tags:

generics

f#

I'm in F# trying to create a generic interface where the type arguments must support addition, so I ended up with something like this:

type IMyInterface<'b when 'b : (static member (+) : 'b * 'b -> 'b)> =
    abstract member myFunction : 'b -> 'b

Unfortunately this piece of codes gives me the following error:

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

I found this question regarding the same problem, but I'm not sure I understand why the function has to be marked as inline.

Also, since you cannot make an abstract member inline, does there exist another solution I could use in my specific example? Or do I have to find a whole other way to acheive this?

like image 246
Thorkil Holm-Jacobsen Avatar asked Dec 26 '22 06:12

Thorkil Holm-Jacobsen


2 Answers

First of all, I think I do not fully understand why you need to state that the type parameter of the interface supports addition - I suppose addition would be used in the implementation of MyFunction, in which case the callers of the interface do not need to know about it. If you want to expose addition, you could simply add it as a separate method:

type IMyInterface<'T> =
  abstract Add : 'T * 'T -> 'T
  abstract MyFunction : 'T -> 'T

I think static member constraints do not work very well in other places than in inline functions or inline static members (but I may be wrong). If you need to use numeric operations inside a generic type, you can use a trick that captures the implementations (in an inline method) and stores them in an interface. There is an example of this at the end of my blog about generic math in F#.

The trick is to define an interface with the numeric operations you need and pass it as an additional argument to the constructor:

type IAddition<'T> =
  abstract Add : 'T * 'T -> 'T

type MyType<'T>(a:'T, b:'T, add:IAddition<'T>) =
  member x.Sum = add.Add(a, b)

So far, this is using interfaces in a standard .NET way - the interface represents the numeric operations and we invoke them via the interface. Now, the trick is to add inline method Create that takes just the two arguments and requires + as a static constraint. The method can then implement the interface and pass it to the MyType as a last parameter:

  static member inline Create(a:^N, b:^N) =
    let ops = 
      { new IAddition< ^N > with
          member x.Add(a, b) = a + b }
    MyType< ^N >(a, b, ops)

Now you can write MyType<_>.Create(1, 2) and the + operation for integers will be automatically captured and stored in an interface (which is easier to work with in the rest of your code).

like image 123
Tomas Petricek Avatar answered Jan 18 '23 23:01

Tomas Petricek


Statically Resolved Type Parameters on MSDN may clarify the need for inline. The bottom line is you can't put the constraint on the interface, and it's not precisely where it's needed either. (Do you really want an interface to "leak" what amounts to an implementation detail? Remember, this constraint only exists at compile-time.) Functions may be inline, so this works:

type IMyInterface<'b> =
    abstract member myFunction : 'b -> 'b

// (^b : (static member (+) ...) constraint is inferred
let inline makeMyInterface() =
  { new IMyInterface<_> with
      member x.myFunction b = b + b }
like image 27
Daniel Avatar answered Jan 19 '23 00:01

Daniel