2.4.3 模式匹配(Pattern matching)

728 篇文章 1 订阅
349 篇文章 0 订阅

2.4.3 模式匹配(Pattern matching)

 

当使用函数数据类型时,我们知道得更多的是有关将要处理的这种类型的结构,这个属性有一很好的示例,就是差别联合。处理此类型时,我们肯定知道能得到什么类型的值(比如,在我们前面的示例中,它可能是矩形、椭圆或组合形状)。

要写处理差别联合的函数,必须指定每种情况下程序应该做什么。这种结构可能在很多方面类似于 C# 中的 switch 语句,但也几个重要的差异。首先,我们看一下如何用C# 的中switch 语句模拟差别联合,处理数据结构。清单 2.6 展示了如何打印给定图形的信息。

 

清单 2.6 用 switch 语句检查不同的情况 (C#)

 

switch(shape.Tag) {  [1]

  caseShapeType.Rectangle:

   var rc = (Rectangle)shape;

    Console.WriteLine("rectangle{0}-{1}", rc.From, rc.To);

    break;

  caseShapeType.Composed:

    Console.WriteLine("composed");

    break;

}

 

清单 2.6 假定 shape 类型有一个属性 Tag,指定它表示哪种类型的形状,这相当于 F# 差别联合,它也能测试值表示哪种可能的情况。当值是矩形时,打印有关该矩形的信息。要在在 C# 中做到这样,首先必须把形状强制转换(cast)(形状有一个抽象的基类 Shape 类型)成其派生类的类型(在这示例是Rectangle),然后,才能最终访问矩形的特有属性。在函数编程中,我们使用这种结构类型比在正式 C# 中要多,所以,需要一种更容易、更安全的方式访问的特定情况的属性。

清单 2.6 中值得最后提一下的,是代码只包含了三种情况中的两种。如果形状表示椭圆,switch 语句不会做任何事情。在 C# 中,有时可能是正确的行为,但它不适用于函数程序。我们说过,在函数编程中一切都是表达式,因此,我们可以从函数版本 switch 中返回某个值。在这种情况下,我们一定要涵盖所有情况,否则程序可能不知道要返回什么值。

在清单 2.7 中,我们将看到 F# 替代 C# 中 switch 语句的这个结构,被称为匹配(match);我们用它来计算形状的面积。

 

清单 2.7 用模式匹配计算面积 (F#)

 

match shape with

| Rectangle(pfrom, pto) ->

  rectangleArea(pfrom,pto)      [1]

| Ellipse(pfrom, pto) ->

  ellipseArea(pfrom,pto)

| Composed(Rectangle(from1, to1),Rectangle(from2, to2))  | [2]

  whenisNestedRectangle(from2, to2, from1, to1) ->       |

    rectangleArea(from1,to1)

| Composed(shape1, shape2) ->   [3]

  letarea1 = shapeArea(shape1)

  letarea2 = shapeArea(shape2)

  area1+ area2 - (intersectionArea(shape1, shape2))

 

与 C# 的 switch 结构最重要区别是,在 F# 中,可以根据模式解析我们想要匹配的值。在清单 2.7 中,所有分支都用到了。不同的分支(用 | 符号表示),通常称为模式(patterns,或断言 Guard,[ 断言是一种用于强化模式匹配的函数结构。可在一个模式上做一些简单的变量测试和比较。在函数定义中使用断言必须以关键字when开头])。

计算矩形面积[1],需要得到确定矩形的两个点。当使用匹配时,只需提供两个名称(pfrom 和 pto),形状表示为矩形,该分支执行,匹配结构造会把值赋给这些名称。清单 2.7 为了简化,只用工具函数计算实际的数字。

第二种情况是椭圆,非常类似于第一种情况。第三种情况更有意义[2],这个模式相当复杂,指定在这个分支下模式需要符合的条件(在符号| 和 [->] 之间)。只有当形状是Composed 类型,而且组成复合形状的两个形状都是矩形时,该模式才匹配。在这个 Composed 模式内,没有指定值的名称,而是指定另外两个模式(用了两次 Rectangle),这称为嵌套模式(nested pattern),非常有用。此外,这个模式还包含了 when 子句,它允许我们指定任意条件。在这个示例中,我们调用 isNestedRectangle 函数,用来检查第二矩形是否嵌套在第一个矩形中。如果匹配这个模式,我们就获得了两个矩形的信息,还知道第二个矩形嵌套在第一个矩形中,所以,我们可以优化计算,只要返回第一个矩形的面积。

F# 编译器能得到类型结构的完整信息,因此,可以验证是否缺失了某种情况。如果我们忘记了最后的一个模式[3],它会警告我们还有一种情况没有处理(例如,由两个椭圆组成的形状)。实现最后一个分支更加困难,如果通常组合两个矩形,那么,在第三分支中的优化就很有用了。类似于一级函数,差别联合和模式匹配也是函数概念,能够让我们以更简单的概念来思考问题。

 

用函数式数据结构思考问题

 

虽然在 C# 中没有简单的方式创建差别联合类型,但这个概念对 C# 开发人员仍然有用,一旦熟悉以后,你会发现许多编程问题都可以用差别联合表示。

如果你熟悉面向对象的设计模式,你会知道我们在前面的示例中使用了组合(composite)。我们可以通过组合其他两个形状,来创建更复杂的形状。在函数编程中,我们将更多地使用差别联合来表示程序的数据,所以,在很多情况下组合设计模式将会消失。

如果最终用 C# 解决这个问题,差别联合会表示为分层的类(有一个基类,每种情况再派生一个类)。但我们在思想上,仍然可以使用简单的概念,使考虑应用程序的体系结构更容易。在函数编程中,经常使用这种数据结构,这也就是为什么函数语言会支持更灵活的模式匹配结构。清单 2.7 演示了 F# 的 match 表达式可以简化复杂结构。在本书中,我们会反复看到这种简化:适当的模型和来自语言帮助可以极大地增加代码的可读性。

 

我们说过,F# 编译器可以验证在模式匹配中是否缺失某种情况,这得益于 F# 语言是静态类型,它对其他许多方面也有帮助。在下一节,我们将讨论这些优点,并用一个示例来突出 F# 编译时检查的目标。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值