Does F# have generic arithmetic support?

2019-01-11 07:27发布

问题:

Does F# have the same problem as C# where you can't directly use arithmetic operators with generic T types?

Can you write a generic Sum function that would return the sum of any value that supports the arithmetic addition?

回答1:

As brian mentioned, there is some built-in support for generic arithmethic and you can use 'static constraints' which allow you to define some generic functions yourself (although this is a bit limited).

In addition to this, you can also use dynamic 'numeric associations', which is a bit slower when using in a function, but it can be nicely used for example to define your own vector or matrix type. Here is an example:

#r "FSharp.PowerPack.dll"
open Microsoft.FSharp.Math

let twoTimesLarger (n:'a) (m:'a) = 
  let ops = GlobalAssociations.GetNumericAssociation<'a>()
  let sel = if ops.Compare(n, m) > 0 then n else m
  ops.Multiply(sel, ops.Add(ops.One, ops.One))

We first need to reference F# PowerPack library which contains the functionality. Then we define a generic function with a signature 'a -> 'a -> 'a. The first line dynamically gets numeric operations for working with the type 'a (it essentially uses some lookup table with the type as the key). Then you can use methods of the numeric operations object to perform things like multiplication, addition (Multiply, Add) and many others. The function works with any numbers:

twoTimesLarger 3 4  
twoTimesLarger 2.3 2.4
twoTimesLarger "a" "b" // Throws an exception!

When you define your own numeric type, you can define its numeric operations and register them using GlobalAssociations.RegisterNumericAssociation. I believe this also means that you'll be able to use built-in F# Matrix<YourType> and Vector<YourType> after registering the operations.



回答2:

F# has some limited support for this. A good general solution probably involves type classes, which are not supported by the CLR in general, or F# in particular.

F# has overloaded arithmetic operators using 'static member constraints' and 'inline' functions. This is the magic that enables e.g. the + operator to work on both ints and floats. You can write inline functions whose implementations are based on the built in math operators and make some progress, but it is non-trivial to do in general. You might check out e.g. the source code to Array.sum (in array.fs in FSharp.Core) in the F# source distribution that comes along with the CTP to get a feel.

See also the 'static member constraints' and 'simulate type classes' part of this answer:

Functions with generic parameter types

as well as various bits of the library like

http://msdn.microsoft.com/en-us/library/ee370581(VS.100).aspx

http://msdn.microsoft.com/en-us/library/ee340262(VS.100).aspx



回答3:

You could do something like this.

let inline sum<'a when 'a : (static member (+) : 'a -> 'a -> 'a)> a b =
    a + b

let result = sum<int> 3 4

However if I try let result = sum 3 4 I get the error "type ambiguity inherent in the use of the operator '( + )'"



回答4:

The best mechanism I'm aware of for performing generic arithmetic is type classes, which sadly neither C#, F#, nor the .Net runtime in general support. However, you can simulate them yourself by hand, as mentioned in this blog post:

Type Classes Are The Secret Sauce

That technique should work in C# 2.0 or later (using anonymous delegates / lambdas).

Often people turn to interfaces, but run into a couple problems

  1. You can't declare that existing type implement an interface, so you can't define instance of that interface for built in types like int.
  2. Interfaces can't constrain the type of other arguments to methods.

An interface declares that, for all implementations, all the methods on that interface take the same implicit 'this' parameter type. If Foo implements some interface, then obviously the 'this' parameter must be of type Foo for that implementation. But there's no way to require that other method parameters also be of type Foo.

Type classes allow you to (among other things) perform this kind of constraint on all method parameters, not just the first parameter.

As mentioned in the article cited earlier, you can simulate type classes by passing function tables as explicit arguments.

(Community wiki: would post an example from that article translated into C# here, but ran out of time with long-winded explaination)