最近开始学习Scala,相较于学习Haskell的过程来看,Scala真是直观得多,友好得多,更容易上手。以前写过关于从熟悉的Java和JavaScript来逐步学习Groovy和Haskell的文章,这以后再来学习Scala的话,就可以不断比较了。如果和我一样有Java经验的话但是从来没有接触过Scala的话,建议先阅读这篇文章, A Scala Tutorial for Java Programmers ,一边比较,一边熟悉,同时配套的还有这个, Scala for Java programmers – Joakim Ohlrogge & Enno Runne ,Youtube上的视频,很直观,然后再从Scala官网的文档上面逐步涉入。
这里的模式匹配可能是历经函数式编程才引入的概念,是广泛存在于编程语言函数使用中的,而并非以前接触的“正则表达式”这样仅仅用于字符串处理的特性。在此之前,先来看看Haskell中的模式匹配,我在这里曾经举过这个阶乘的例子:
factorial :: (Integral a) => a -> a factorial 0 = 1 factorial n = n * factorial (n - 1)
根本不需要多余的解释,一眼就看懂。模式匹配在这里起到了if-else的作用,对于逻辑的执行,起到了一个“变化点”的作用。在以往传统的静态语言中,要在程序中植入“变化点”,要么就是if-else语句(本质上switch-case和使用Map去寻找匹配的value也属于if-else),要么就是多态,要么就是方法重载。现在我们看到了一个根据参数改变程序执行逻辑步骤的新武器。虽然说,这个例子可以说和使用if-else相比,似乎没有太大的区别,但是在存在不同的参数组合情况的时候,这个写法的优势就体现出来了:
translate :: String -> String translate ('$':x) = "Dollar: " ++ x translate (_:x) = "Unknown: " ++ x
其中的下划线“_”就是通配符,这种写法上的pattern很像带有default语句的switch-case,最后一个通配符保证了不会有异常抛出,所有case都被涵盖。
再挪到Scala里面看模式匹配,上面的情况也都能够支持。模式匹配可不一定只作用在单个参数作为整体来实现匹配,参数还可以拆分,比如说:
List(1,2,3) match{ case List(_,_,3) => println("ok") }
这就是忽略了前两个参数,直接比对第三个参数是否为3。当然,除了上面的情形,模式匹配还可以匹配参数的类型。
不止作用在参数的级别上,还可以作用在类和对象的级别上,比如Scala官网首页上面的这个例子:
// Define a set of case classes for representing binary trees. sealed abstract class Tree case class Node(elem: Int, left: Tree, right: Tree) extends Tree case object Leaf extends Tree // Return the in-order traversal sequence of a given tree. def inOrder(t: Tree): List[Int] = t match { case Node(e, l, r) => inOrder(l) ::: List(e) ::: inOrder(r) case Leaf => List() }
Tree本身可以有两种类型的实现,一种是Node,它是个类,接受本身的值、左子树、右子树这三个构造参数;另一种是Leaf,就是一个叶子实例(不是类)。那么在实现中序遍历的inOrder方法的时候,如果是分支节点,那么就递归执行中序遍历的方法(左子树->节点自己->右子树),然后把着三个结果List拼接起来;否则对于叶子节点,就创建一个空的List。
在我们的印象中,传统语言的多态实现,一定是基于“类和对象”的,换言之,在运行时才能确定执行某一个接口(或者抽象类)方法的实体到底是谁(哪个对象)。但是在这里的模式匹配上,这个变化点被移到了函数(或者说方法)上,看起来实现的功能是类似的,但是二者各有优劣:
上面的这些模式匹配方式组合起来,可以执行一些复杂的匹配,比如基于构造器:
case Node(_, Node(1,_,_), Node(2,_,_))
这样的,是要求构造器的三个参数中,左子树参数的值是1,右子树参数是2。
甚至可以这样:
case Node(_, nodeToReturn@Node(1,_,_), Node(1,_,_)) => nodeToReturn
表示碰到这个case的时候,返回构造器的第二个参数。