zoukankan      html  css  js  c++  java
  • scala 模式匹配详解 1

    什么是模式?

    一些刚从java转到scala的同学在开发的过程中犹如深陷沼泽,因为很多的概念或风格不确定,scala里有很多的坑,模式匹配也算一个。我整理了一下自己所理解的概念,以及一些例子。这个系列最好有些scala的基本经验,或者接触过一些其他函数式语言。

    要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式并不是设计模式里的模式。
    而是数据结构上的,这个模式用于描述一个结构的组成。

    我们很容易联想到“正则表达”里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串。


    比如正则表达式里 “^A.*” 这个pattern 表示以A开头、后续一个或多个字符组成的字符串;
    List(“A”, _,  _*) 也是个pattern,表示第一个元素是”A”,后续一个或多个元素的List。

    狭义的看,模式可以当作对某个类型,其内部数据在结构上抽象出来的表达式。如上面的List(“A”, _, _*) 就是一种List结构的pattern。
    模式匹配(pattern-matching)则是匹配变量是否符合这种pattern。比如List(“A”,”B”)和List(“A”,”X”,”Y”) 就符合上面的pattern,而List(“X”)则不符合。

    直观的看几个例子:
    // 匹配一个数组,它由三个元素组成,第一个元素为1,第二个元素为2,第三个元素为3
    scala> Array(1,2,3) match { case Array(1,2,3) => println(“ok”)}
    ok
    // 匹配一个数组,它至少由一个元素组成,第一个元素为1
    scala> Array(1,2,3) match { case Array(1,_*) => println(“ok”)}
    ok
    // 匹配一个List,它由三个元素组成,第一个元素为“A”,第二个元素任意类型,第三个元素为”C”
    scala> List(“A”,”B”,”C”) match{ case List(“A”,_,”C”) => println(“ok”) }
    ok

    例子中的:Array(1,2,3) ,List(“A”,_,”C”) 等都是模式,表示由指定元素组成的某种类型。
    当然模式也不仅仅是表示某种结构的,还可以是常量,或类型,如:
    scala> val a = 100
    a: Int = 100

    // 常量模式,如果a与100相等则匹配成功
    scala> a match { case 100 => println(“ok”) }
    ok

    // 类型模式,如果a是Int类型就匹配成功
    scala> a match { case _:Int => println(“ok”) }
    ok

    在 scala里对pattern有明确的定义,在形式上有以下几种pattern:

    1) 常量模式(constant patterns) 包含常量变量和常量字面量

    scala> val site = “alibaba.com”
    scala> site match { case “alibaba.com” => println(“ok”) }

    scala> val ALIBABA=”alibaba.com”
    scala> def foo(s:String) { s match { case ALIBABA => println(“ok”) } } //注意这里常量必须以大写字母开头

    常量模式和普通的 if 比较两个对象是否相等(equals) 没有区别,并没有感觉到什么威力

    2) 变量模式(variable patterns)
    确切的说单纯的变量模式没有匹配判断的过程,只是把传入的对象给起了一个新的变量名。
    scala> site match { case whateverName => println(whateverName) }
    上面把要匹配的 site对象用 whateverName 变量名代替,所以它总会匹配成功。
    不过这里有个约定,对于变量,要求必须是以小写字母开头,否则会把它对待成一个常量变量,
    比如上面的whateverName 如果写成WhateverName 就会去找这个WhateverName的变量,如果找到则比较相等性,找不到则出错。

    变量模式通常不会单独使用,而是在多种模式组合时使用,比如 List(1,2) match{ case List(x,2) => println(x) }
    里面的x就是对匹配到的第一个元素用变量x标记。

    3) 通配符模式(wildcard patterns)
    通配符用下划线表示:”_” ,可以理解成一个特殊的变量或占位符。
    单纯的通配符模式通常在模式匹配的最后一行出现,case _ =>  它可以匹配任何对象,用于处理所有其它匹配不成功的情况。

    通配符模式也常和其他模式组合使用:
    scala> List(1,2,3) match{ case List(_,_,3) => println(“ok”) }
    上面的 List(_,_,3) 里用了2个通配符表示第一个和第二个元素,这2个元素可以是任意类型
    通配符通常用于代表所不关心的部分,它不像变量模式可以后续的逻辑中使用这个变量。

    4) 构造器模式(constructor patterns)
    这个是真正能体现模式匹配威力的一个模式!

    我们来定义一个二叉树:

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    trait Node //抽象节点
    case class TreeNode(v:String, left:Node, right:Node) extends Node //具体的节点实现,有两个子节点
    case class Tree(root:TreeNode)  //Tree,构造参数是根节点
    // Exiting paste mode, now interpreting.

    这样我们构造一个根节点含有2个子节点的数:
    scala>val tree = Tree(TreeNode(“root”,TreeNode(“left”,null,null),TreeNode(“right”,null,null)))

    如果我们期望一个树的构成是根节点的左子节点值为”left”,右子节点值为”right”并且右子节点没有子节点
    那么可以用下面的方式匹配:
    scala> tree.root match { case TreeNode(_,TreeNode(“left”,_,_), TreeNode(“right”,null,null)) => println(“bingo”) }

    只要一行代码就可以很清楚的描述,如果用java实现,是不是没这么直观呢?

    5) 类型模式(type patterns)
    类型模式很简单,就是判断对象是否是某种类型:
    scala> “hello” match { case _:String => println(“ok”) }

    跟 isInstanceOf 判断类型的效果一样,需要注意的是scala匹配泛型时要注意,
    比如
    scala> def foo(a:Any) { a match { case a :List[String] => println(“ok”); case _ => } }
    如果使用了泛型,它会被擦拭掉,如同java的做法,所以上面的 List[String] 里的String运行时并不能检测
    foo(List(“A”))  和 foo(List(2)) 都可以匹配成功。实际上上面的语句编译时就会给出警告,但并不出错。
    通常对于泛型直接用通配符替代,上面的写为 case a : List[_] => …

    6) 变量绑定模式 (variable binding patterns)
    这个和前边的变量模式有什么不同?看一下代码就清楚了:

    依然是上面的TreeNode,如果我们希望匹配到左边节点值为”left”就返回这个节点的话:
    scala> tree.root match { case TreeNode(_, leftNode@TreeNode(“left”,_,_), _) => leftNode }
    用@符号绑定 leftNode变量到匹配到的左节点上,只有匹配成功才会绑定

    另外解释一下抽取器模式(extractor patterns),一些资料里也会提到这个模式
    抽取器是一种实现模式匹配的技术方式,在表现上,抽取器模式与构造器模式一致,都是 case A(e1,e2) => 这样的形式。
    在《Programming in scala》一书中提到 序列模式(sequence patterns),针对所有SeqFactory的子类,它其实就是抽取器模式。
    在表达形式上 case List(1,2,3) => … 或 case Array(“a”,”b”) => …  看着与构造器模式一模一样(就是背后实现有所不同)
    所以在模式的表现形式上,不适合把它划为一类,非要把 序列模式 与构造器模式区分的话,也是从它们背后的实现上,而不是表现上。

    另外《Programming in scala》一书中也单独提到 元组模式(tuple patterns), 元组模式本质上也是一个构造器模式。

    了解完模式匹配的感念后,我们后续再看一下scala里是怎么实现模式匹配的

  • 相关阅读:
    changing a pointer rather than erasing memory cells
    验证码识别 edge enhancement 轮廓增强 region finding 区域查找
    Manipulating Data Structures
    passing parameters by value is inefficient when the parameters represent large blocks of data
    Aliasing 走样
    Artificial Intelligence Research Methodologies 人工智能研究方法
    Thread safety
    include pointers as a primitive data type
    flat file
    functional cohesion
  • 原文地址:https://www.cnblogs.com/yudar/p/5095942.html
Copyright © 2011-2022 走看看