5.3.2 使用可选值

    技术2022-05-19  28

    5.3.2 使用可选值

     

        到目前为止,我们已经看到如何声明差别联合类型,以及如何使用识别器创建值。现在,我们将学习如何编写代码,读取值。看过之后F# 示例之后,我们将在 C# 中实现相同的代码,使用我们前面提到过的 Tag 属性的表示形式。

     

    在 F# 中匹配差别联合

     

        当使用差别联合时,我们总是要为所有可能的选项编写代码,因为,我们不知道这个值到底表示哪一个。加快一下早先的类似情况, 我们必须测试列表是空列表。还是 cons cell。我们使用模式匹配来完成此操作:match 构造使我们能够根据几种模式测试值。我们可以用相同的功能,处理差别联合,这一次,除了识别器写模式。清单 5.5 显示了一个示例,获取下一计划事件的发生。

     

    Listing 5.5 Calculating the next occurrence of an event (F#)

     

    let getNextOccurrence(schedule) =   match schedule with   | Never -> DateTime.MaxValue   | Once(eventDate) –>     if (eventDate > DateTime.Now) then eventDate     else DateTime.MaxValue   | Repeatedly(startDate, interval) –>     let secondsFromFirst = (DateTime.Now - startDate).TotalSeconds     let q = secondsFromFirst / interval.TotalSeconds     let q = max q 0.0     startDate.AddSeconds       (interval.TotalSeconds * (Math.Floor(q) + 1.0))

     

        这个示例是有点复杂,但它展示了典型的 F# 程序结构。我们使用标准 .NET DateTime 和 TimeSpan 结构处理日期和时间。我们使用模式匹配测试已为我们选择了哪一个计划。在第一种情况中,我们返回 DateTime.MaxValue,这是一个特殊值,我们用来表示事件在任意未来的日期内都没有计划。在第二种情况,我们返回的事件的日期,如果它还没有发生。最后一种情况(重复事件)更复杂,我们首先计算事件在过去发生了多少次,并返回下一次发生。你可以看到,我们在代码中两次声明 q 值,这称为隐藏值,如果我们想把复杂的计算拆成两个或多个步骤,这是很有用的,可以确保我们不会意外地使用中间值。

        如你所见,该模式用于测试一个值是否值匹配特定的鉴别器,与我们已经用于构造值是完全相同的。模式也提取存储的值作为参数值,并将它们分配给新的值(分别是 eventDate 和 带 interval 的 startDate ),这样,我们就可以立即使用它们。

     

    在 C# 中模仿差别联合

     

        接下来,我们将看看在 C# 中相同的功能实现。我们早先已经提到有关涉及到的类,因此,假设它们已经实现,且只看使用它们的代码。在本章后面,我们将看另一个有关可选值的示例,包括完整的 C# 实现,因此,您将看到如何编写 C# 类层次结构,具有相同的属性,像 F# 的差别联合一样。

     

    提示

     

        如果您想要查看此示例完整的源代码,包括类声明,可以从本书的网站 http://www.functional-programming.net,或出版商的网站http://www.manning.com/Real-WorldFunctionalProgramming 下载。

        在我们看清单 5.6 的 C# 版本示例之前,有一件重要的事情要注意。有一种情况,当我们已经有类层次结构表示实现的计划(例如,在一个共享库),它并不包含获取下一次发生的方法。我们需要添加新的功能到应用程序中的模块,不能轻松地将虚方法添加到类的基类 Schedule。此外,我们要保持这个功能在代码中的一个单独的地方,保持与这个计算相关的一切,同一个地方,同一个文件中。

    Listing 5.6 Calculating the next occurrence of an event (C#)

     

    DateTime GetNextOccurrence(Schedule schedule) {   switch(schedule.Tag) {   case ScheduleType.Never:     return DateTime.MaxValue;   case ScheduleType.Once:     var once = (Once)schedule;     return once.EventDate > DateTime.Now ?       once.EventDate : DateTime.MaxValue;

      case ScheduleType.Repeatedly:      var rp = (Repeatedly)schedule;      var secondsFromFirst = (DateTime.Now - rp.StartDate).TotalSeconds;      double q = secondsFromFirst / rp.Interval.TotalSeconds;      q = Math.Max(q, 0.0);      return rp.StartDate.AddSeconds        (rp.Interval.TotalSeconds * (Math.Floor(q) + 1.0));    default:       throw new InvalidOperationException();   } }

     

        C# 版本中所使用的算法与 F# 版本是一样的,那么,唯一的区别我们是如何区分选项,以及如何读取存储在选项中的值。在 F# 中,是用模式匹配实现的。在 C# 版本,我们使用 switch,这是 C# 的类似于  F# 中的 match 结构。这是可能的,因为我们已在基类中有 Tag 属性,和一个枚举,它告诉我们对象表示什么种类的计划。否则,我们将必须使用有一个动态类型测试序列的 if 语句。另外,读取值,在 F# 中自动完成,现在是有点困难。我们必须转换计划到具体的类,以读取它的属性。

        实际上,对于差别联合,C# 版本的代码是非常接近于F# 编译器所使用的 .NET  表示。这意味着,前面的两个示例编译后基本上相同。不过,函数编程更强调这种数据类型,这就是为什么在 F# 中,更容易写此代码。


    最新回复(0)