事件(Microsoft.FSharp.Control.Event)模块
可以把 F# 中的事件看做是函数的集合,能够通过函数调用来触发。其思想是,函数本身注册成事件,即函数的集合,等待事件发生的通知;然后,触发函数发出事件已经发生的通知,引发所有添加到事件中的函数被执行。
我们将讨论事件模块中的下列功能:
创建和处理事件:使用 create 和 add 函数来创建和处理事件的基础;
筛选(filter)函数:筛选进入事件中数据的函数;
分区(partition)函数:把进入事件中数据拆分成两部分的函数;
映射(map)函数:在数据到达事件处理程序之前进行映射的函数。
创建和处理事件
第一个示例看一个简单的事件,通过调用事件(Event)对象的构造函数来创建,应该传递一个类型参数给构造,表示想要的事件类型;这个对象包含触发(Trigger)函数和表示事件本身的发布(Publish)属性。使用事件的 Publish 属性的 Add 函数添加处理程序方法,最后,用 Trigger 函数触发这个事件:
let event = new Event<string>()
event.Publish.Add(fun x -> printfn"%s" x)
event.Trigger "hello"
代码的运行结果如下:
hello
除了这个事件的基本功能之外,F# 的事件模块还提供了大量的函数,来筛选和分区事件,进行细粒度控制哪些数据传递给哪些事件处理程序。
筛选(filter)函数
下面的示例演示了如何使用事件模块的筛选函数,使数据在到达事件处理程序之前被筛选。在这个示例中,筛选那些以 H 开头的字符串数据发送给事件处理程序。
let event = new Event<string>()
let newEvent = event.Publish |>Event.filter (fun x -> x.StartsWith("H"))
newEvent.Add(fun x -> printfn "newevent: %s" x)
event.Trigger "Harry"
event.Trigger "Jane"
event.Trigger "Hillary"
event.Trigger "John"
event.Trigger "Henry"
代码的运行结果如下:
new event: Harry
new event: Hillary
new event: Henry
分区(partition)函数
事件模块的分区函数与筛选函数相似,只是它返回了两个事件,一个是分区函数返回假所触发的数据,另一个是分区函数返回真所触发的数据。下面的示例有演示:
let event = new Event<string>()
let hData, nonHData = event.Publish |>Event.partition (fun x -> true)
let x = Event.partition
hData.Add(fun x -> printfn "H data:%s" x)
nonHData.Add(fun x -> printfn "NoneH data: %s" x)
event.Trigger "Harry"
event.Trigger "Jane"
event.Trigger "Hillary"
event.Trigger "John"
event.Trigger "Henry"
代码的运行结果如下:
H data: Harry
None H data: Jane
H data: Hillary
None H data: John
H data: Henry
映射(map)函数
在数据到达事件处理程序之前进行转换也是可能的,这要乃至事件模块中提供的映射函数。下面的示例演示了如何使用:
let event = new Event<string>()
let newEvent = event.Publish |>Event.map (fun x -> "Mapped data: " + x)
newEvent.Add(fun x -> printfn"%s" x)
event.Trigger "Harry"
event.Trigger "Sally"
代码的运行结果如下:
Mapped data: Harry
Mapped data: Sally
这一节只对 F# 中的事件提供了一个概览,在第八章讨论用户界面编程时会有更详细的介绍,因为那才是事件的最主要用途。
功能包(power pack)中有大量没有包含在FSharp.Core.dll 中的功能,这既有空间问题的原因,还因为它是带有试验性质,有可能比FSharp.Core.dll 的进化更快。它提供的模块能很好地与 OCaml 相兼容,有额外的集合,额外的数学函数,异步工作流(在第十章讨论),支持用fslex 和 fsyacc 进行文本解析的函数。下面我们就讨论Microsoft.FSharp.Math,这个命名空间包含了几个与数学相关的模块:任意精度的整数和理由、向量、矩阵和复数。
Microsoft.FSharp.Math命名空间
设计 Microsoft.FSharp.Math命名空间,是 F# 保证 F# 库包括了一些基础构造定义的使用更加广泛,如图形、数学、科学、工程等应用。首先,我们先简单地看一下这个模块的组成,然后,再分别看详细的示例。
它包含任意精度数,这是其值没有上限的数,包括在模块 BigInt 和 BigNum 中。典型的用途是在搜索大质数的程序中,可能用于加密应用。
模块 Matrix、Vector、RowVector 和 Notations 都包含了与矩阵和向量相关的运算。矩阵(Matrices)是按行列排列的一组数字集合,构成矩形数组;向量(Vectors)是一列数字,像只有一列的矩阵,但是单独的类型。向量的量由大小和方向描述,因此,二维向量由二个座标确定,三维向量由三个座标确定,等等。因此,向量的矩阵表示,由一列数字组成,而行数取决于向量的维度。
有一个模块 Complex,用于处理复数。复数(complex numbers)是许多不规则图形类型的基础,因此,我们会演示如何使用 F# 的复数库去画最著名的不规则图形,芒德布罗集合(Mandelbrot set)。反复迭代下面的等式,可以产生芒德布罗集合:
Cn+1 =Cn^2 + c
在这个序列中的下一个数等于当前数的平方加上起初数。如果反复迭代这个等式,停留在复数 C(1, 1i) 和 C(-1, -1i) 之间,那么起初复数就是芒德布罗集合的成员。下面的 F# 代码就能实现:
#r
@"FSharp.PowerPack.dll"
open Microsoft.FSharp.Math
//openMicrosoft.FSharp.Math.Notation
let cMax = complex 1.0 1.0
let cMin = complex -1.0 -1.0
let iterations = 18
let isInMandelbrotSet c0 =
let rec check n c =
(n = iterations)
|| (cMin < c) && (c < cMax)&& check (n + 1) ((c * c) + c0)
check 0 c0
[
1、需要引用 Fsharp.PowerPack;
2、没有且不需要 Microsoft.FSharp.Math.Notation 命名空间;
3、把or 要改成 ||
]
函数 isInMandelbrotSet 测试一个复数是否在芒德布罗集合中,通过递归调用 check 函数,每次用新 c 值计算 ((c * c) c0),直到这个复数在常量cMax 和 cMin 之间,或者迭代的次数超出常量 iterations。如果已经到达迭代次数iterations,那么这个数就是集合中的一个成员,否则,就不是。
因为复数由两个数字组成,因此,它可以用二维平面表示。芒德布罗复数介于 C(1, 1i) 和 C(-1, -1i) 之间,因此,画的这个平面要有一点原点,即点 0, 0,在中心,它的轴向两个方向延伸至最大值 1.0 和最小值 -1.0,如图 7-1 右边的平面。然而,当它应用到计算机屏幕上的像素时,必须考虑到平面的原点在左[原文为右]上角,向右、向下扩展。因为这种平面是由像素组成的,它是离散值,通常由整数表示,范围在 0 到 1600 之间,如图 7-1 左边的平面。
图 7-1. 位图平面与复数平面
应用程序必须把位图平面的点映射到复数平面,这样,才可以知道一个像素是否是在这个复数平面中。
只用几行 F# 代码就能很容易实现这个映射:
open Microsoft.FSharp.Math
let scalingFactor = 1.0 / 200.0
let offset = -1.0
let mapPlane (x, y) =
let fx = ((float x) * scalingFactor) + offset
let fy = ((float y) * scalingFactor) + offset
complex fx fy
一旦完成,只需要遍历位图平面上的所有点,用mapPlane 函数把它们映射到复数平面;然后,需要用isInMandelbrotSet 函数测试这个复数是否在芒德布罗集合中;最后,再设置像素的颜色。
完整程序如下:
open System
open System.Drawing
open System.Windows.Forms
open Microsoft.FSharp.Math
let cMax = complex 1.0 1.0
let cMin = complex -1.0 -1.0
let iterations = 18
let isInMandelbrotSet c0 =
let rec check n c =
(n = iterations)
|| (cMin < c)
&& (c < cMax)
&& check (n + 1) ((c * c) + c0)
check 0 c0
let scalingFactor = 1.0 / 200.0
let offset = -1.0
let mapPlane (x, y) =
let fx = ((float x) * scalingFactor) + offset
let fy = ((float y) * scalingFactor) + offset
complex fx fy
let form =
let image =
new Bitmap(400, 400)
for x = 0
to image.Width - 1 do
for y = 0
to image.Height - 1 do
let isMember = isInMandelbrotSet ( mapPlane (x, y) )
if isMember
then
image.SetPixel(x,y, Color.Black)
let temp =
new Form()
in
temp.Paint.Add(fun e
->e.Graphics.DrawImage(image, 0, 0))
temp
[<STAThread>]
do
Application.Run(form)
[
1、需要引用 Fsharp.PowerPack、System.Drawing、System.Windows.Forms;
2、如果使用交互模式,把 do
Application.Run(form) 改成 form.ShowDialog()
]
图 7-2 是程序产生的芒德布罗集合图形。
图 7-2 芒德布罗集合
第七章 小结
在这一章我们讨论了许多基础知识,因为,F#库提供了多种不同功能。首先,我们浏览了 FSharp.Core.dll库中的Collections、Reflection、Math 模块;然后,看了一下FSharp.PowerPack.dll,提供了构建所有应用程序都需要使用的优秀函数。Seq 模块是任何实质性的 F# 程序所不能没有的。
接下来三章我们将讨论如何使用 F# 与各种 .NET API 一起完成常见的编程任务。首先,在第八章看一下实现用户界面,然后,在第九章关注数据存取,在第十章讨论分布式应用。
第七章 F# 库(五),码迷,mamicode.com