6.3 处理计划
在本节,我们要把上一节的技术应用到可选值。在处理元组时,我们发现使用函数,有助于处理元组中的元素;类似地,处理可选值时,同样需要使用高阶函数,对可选值中的一个或多个进行某种操作。我们接着上一章的示例,从计划类型开始,然后,看一下选项类型。
在前面的章节中,我们实施了一个表示事件计划的类型。在 F# 中,使用差别联合Schedule(计划)实现,包含三种可选项;三个可选项识别器分别是Never、Once 和 Repeatedly(从不,一次和多次)。在 C# 中,用抽象类 Schedule 表示,有一个名为 tag 的属性,和一个派生类,表示三个选项。在本节,我们会添加处理计划的高阶函数。
假设现在有一个应用程序,想用使用这个计划,最常用操作(尤其是在当今繁忙世界)重新安排计划的事件。比如,把所有已知的事件推迟一周,或者把预定的事件从星期一推迟到星期二。要明确地把这些写出来,可能很困难,因为必须要为计划的三种不同类型都提供代码。
如果我们这样考虑问题,只根据原来的时间计算出新的时间,而不改变计划的其他任何属性;清单 6.7 就实现了这样一个函数。
清单 6.7 计划类型的映射操作 (F# Interactive)
> let mapSchedule rescheduleFuncschedule =
matchschedule with
|Never -> Never
|Once(eventDate) -> Once(rescheduleFunc(eventDate))
|Repeatedly(startDate, interval) –>
Repeatedly(rescheduleFunc(startDate),interval)
;;
val mapSchedule : (DateTime -> DateTime)-> Schedule –> Schedule [1]
操作称为 mapSchedule,是因为它针对计划所包含的所有日期和时间信息,都执行某个操作。当选择 Never 时,直接返回 Never,而不重新计算;当是 Once 时,给定的函数作为参数值,用于计算新的时间;当计划用 Repeatedly 表示时,函数用于计算出事件第一次发生的时间,保持事件之间原有的周期。
如果看一下类型签名[1],可以发现,第一个参数是函数,参数和返回都是 DateTime,这用于计算事件的新计划时间,原计划是最后一个参数,这样的参数顺序,使这个函数有可能使用管道运算符进行调用,正如我们前面元组映射所做的一样。清单 6.8 显示了我们如何使用这个函数处理计划集合。
清单6.8 使用 mapSchedule 函数重订计划 (F# Interactive)
> let schedules =
[Never; Once(DateTime(2008, 1, 1));
Repeatedly(DateTime(2008,1, 2), TimeSpan(24*7, 0, 0)) ];; [1]
val schedules : Schedule list
> for s in schedules do
letnewSchedule = s |> mapSchedule (fun d -> d.AddDays(7.0)) [2]
printfn"%A" newSchedule
;;
Never | [3]
Once 8.1.2008 0:00:00 |
Repeatedly (9.1.2008 0:00:00,7.00:00:00) |
我们首先创建用来测试的计划列表[1]。注意,我们在构建.NET 对象DateTime 和 TimeSpan 时,省略了 new 关键字,这只是F# 在处理简单类型时,在语法上的简化,就像这里的两个。
创建列表之后, 我们遍历所有的计划。在下一行[2],我们使用 mapSchedule 函数,把所有的计划推迟一周。日期的改变,由返回新的 DateTime 对象的lambda 函数指定。当然,可以在这个函数里面实现更复杂的逻辑,执行不同的重新安排。原计划使用管道运算符,作为最后一个参数值传递。可以看到[3],这个操作改变了 Once 计划的日期,以及使用 Repeatedly 选项计划的第一次发生日期。