F#与数学(III) – 自定义数字类型(PartIII)

用全局关联来支持矩阵

我们的数字类型可以被用在一些标准的库函数如List.sum中,因为它们实现一些被List.sum必需的静态成员约束的成员。然而,这种方法对复杂的数据类型如泛型矩阵不工作。如果我们试图用我们的类型写任意矩阵计算,这将是不工作的,因为泛型矩阵类型不能自动的使用我们提供的重载操作符和成员。

为了支持矩阵我们需要提供所谓的数值关联。它是个简单的接口,包含了我们的类型上能被执行的数值操作符号。这个接口需要被注册在被F# PowerPack库维护的全局表中,以使任何F# 代码能够从这个表中得到接口并用它来对我们的类型执行操作。

F# PowerPack库包含了用一个层次表示的一系列接口,这些接口代表了多组我们可能可以提供的数值操作。我们将实现最基本的接口INumeric<'T>来表示一个简单的数值类型。其它可用的接口如IIntegral<'T>提供诸如DivRem(除法,保留余数)操作,或者IFloating<'T>:额外地包含了浮点操作如Sqrt

最简单的实现接口的方式就是用对象表达式和创建一个实现它的单独的值。我们也需要提供一个在GlobalAssociations表中注册实例的操作:

1: module IntegerZ5Associations=

2:   let IntegerZ5Numerics=

3:     { new INumeric<IntegerZ5>with

4:          member z.Zero=IntegerZ5.Zero

5:          member z.One=IntegerZ5.One

6:          member z.Add(a,b)=a+ b

7:          member z.Subtract(a,b)= a- b

8:          member z.Multiply(a,b)= a* b

9:          member z.Equals(Z5 a,Z5 b)= (a= b)

10:         member z.Compare(Z5 a, Z5 b)= compare a b

11:         member z.Negate(a)=0Z- a

12:         member z.Abs(a)= a

13:         member z.Sign(Z5 a)= Math.Sign(a)

14:         member z.ToString(Z5 n,fmt,fmtprovider)=

15:            n.ToString(fmt,fmtprovider)+"(mod 5)"

16:         member z.Parse(s,numstyle,fmtprovider)=

17:            z5(System.Int32.Parse(s,numstyle,fmtprovider)) }

18:

19:   let Init()=

20:     GlobalAssociations.RegisterNumericAssociationIntegerZ5Numerics

就我们已经在IntegerZ5类型中提供的的操作符而言,大部分操作可以被直接执行,因此INumeric<'T>接口的目的就是简单的使这些操作可以间接的被访问。一些其它的操作如Compare,Sign 或者ToString可以用基础整型实现(我们可以通过写一个代替参数的Z5样式提取出来)。

这个module还提供了Init 函数,在GlobalAssociations表中注册我们的实例。理想的情况下,当IntegerZ5类型第一次被使用时,注册将被自动完成,但是不幸地是这很不容易达到。相反,我们会些一个函数,这个函数需要被显式调用(为了使这自动进行,你可以把代码移到IntegerZ5类型的一个静态的构造器内)。

为使module在编译过的库中可见,我们还需要包含它的类型签名到我们之前添加的F#签名文件中去。这可以通过添加下面的代码清单实现:

1: module IntegerZ5Associations=

2:   val IntegerZ5Numerics: INumeric<IntegerZ5>

3:   val Init: unit-> unit

既然我们的库已经完成,我们可以编译并在其它的F#工程中使用创建的程序集。本文中,我们不以创建一个C#兼容的类型为目标,然而,该类型C#将也可用(更重要的是,所有的重载操作符合类型成员在C# 中也将能工作)。

作为最后一步,我们将看看如何在F#交互中加载编译了的库并用我们的类型做一个简单的泛型矩阵类型的计算:

1: #r"FSharp.PowerPack.dll"

2: #r"bin\Debug\FSharp.Numerics.dll"

3:

4: open FSharp.Numerics

5: module MatrixG=Matrix.Generic

6:

7: IntegerZ5Associations.Init()

8:

9:  let ys=MatrixG.init55 (fun a b-> (z5 a))

10: let xs= MatrixG.init55(fun a b-> (z5 b))

11: val ys :Matrix<IntegerZ5> = (...)

12: val xs :Matrix<IntegerZ5> = (...)

13:

14: xs.* ys

15: val it :Matrix<IntegerZ5> =

16:   matrix [[0 (mod 5); 0 (mod 5); 0 (mod 5); 0(mod 5); 0 (mod 5)]

17:           [0 (mod 5); 1 (mod 5); 2 (mod 5); 3(mod 5); 4 (mod 5)]

18:           [0 (mod 5); 2 (mod 5); 4 (mod 5); 1(mod 5); 3 (mod 5)]

19:           [0 (mod 5); 3 (mod 5); 1 (mod 5); 4(mod 5); 2 (mod 5)]

20:           [0 (mod 5); 4 (mod 5); 3 (mod 5); 2(mod 5); 1 (mod 5)]]

引用F# PowerPack和我们的库以后,我们打开命名空间FSharp.Numerics,这个命名空间包含我们的类型并定义了别名为MarixG的module以简化对泛型Matrix类型的访问。然后我们调用Init函数以为我们的类型注册全局的数值关联。

最后,我们构造两个大小为5x5的矩阵,第一个包括沿着一维加IntegerZ5类型的值,第二个阵中的值沿着第二维进行增加。我们用这两个矩阵的点态乘法以产生一个简单的表,得出乘以所有可能的IntegerZ5类型的组合的值后的结果。

 

小结

在这篇文章中,我们研究了实现一个用户自定义的数值类型。尽管你很多时候可能不会需要实现一个自定义数值类型,但是是有用的。除了模运算,你可能还需要实现一个类型,这个类型代表对值乘4(代表颜色) 或者代表三维向量。F# PowerPack库(使用)本文中所描述的技术以实现BigRationalcomplex类型(我在早期的文章中已经介绍了)

当实现一个数值类型时,有相当多的模块你可能要支持。在本文中,我们定义了重载操作符和被静态成员约束所必需的成员,以使我们能使用函数如List.sum。然后我们还定义了数值字段以能够写1Z而不是Z5 1。最后,我们实现了INumeric<'T>接口,这使得从F#PowerPack的泛型矩阵中使用我们的类型成为可能,这我在先前系列的文章中涵盖到了。

 

参考 & 链接

::  [1] 模运算- Wikipedia.org

::  [2] F# PowerPack(源代码和二进制包) - CodePlex

::  [3] 数值运算 - MSDN实时功能编程

::  [4] F#和.NET中的数字库 - MSDN实时功能编程

 

原文链接:http://tomasp.net/blog/fsharp-custom-numeric.aspx

posted @ 2012-04-27 16:43  tryfsharp  阅读(221)  评论(0编辑  收藏  举报