{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 模式匹配\n", "在Scala的模式匹配中,可以使用类型、通配符、序列、正则表达式,甚至可以深入获取对象的状态。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 简单匹配\n", "像是C风格的case语句\n", "\n", "除了偏函数,所有的match语句都必须是完全覆盖所有输入的。当输入类型为Any时,在结尾用case _或case some_name作为默认子句。" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got heads\n", "Got tails\n" ] }, { "data": { "text/plain": [ "\u001b[36mbools\u001b[0m: \u001b[32mSeq\u001b[0m[\u001b[32mBoolean\u001b[0m] = \u001b[33mList\u001b[0m(true, false)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val bools = Seq(true, false)\n", "\n", "for (bool <- bools) {\n", " bool match {\n", " case true => println(\"Got heads\")\n", " case false => println(\"Got tails\")\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "int 1\n", "other int: 2\n", "a double: 2.7\n", "string one\n", "other string: two\n", "other string: four\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for {\n", " x <- Seq(1, 2, 2.7, \"one\", \"two\", \"four\")\n", "} {\n", " var str = x match {\n", " case 1 => \"int 1\"\n", " case i: Int => \"other int: \" + i\n", " case d: Double => \"a double: \" + x\n", " case \"one\" => \"string one\"\n", " case s: String => \"other string: \" + s\n", " case unexpected => \"unexpected value: \"+ unexpected\n", " }\n", " println(str)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "unexpected匹配任意输入,x的值被赋给unex这个变量。由于未给出任何类型说明,unexpected的类型被推断为Any,起到default语句的作用。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.1 switch注解和tableswitch优化\n", "在简单的匹配表达式中,建议使用@switch注解,如果模式匹配不能编译成tableswitch或者lookupswitch,该注解将报错。tableswitch或者lookupswitch具有更好的性能,比决策树的方式更加高效,可以直接跳到要匹配的结果上。" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[36mscala.annotation.switch\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import scala.annotation.switch" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mi\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m1\u001b[0m\n", "\u001b[36mx\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"One\"\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "val i = 1\n", "val x = (i: @switch) match {\n", " case 1 => \"One\"\n", " case 2 => \"Two\"\n", " case _ => \"Other\"\n", "}" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mi\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m1\u001b[0m\n", "\u001b[36mTwo\u001b[0m: \u001b[32mInt\u001b[0m = \u001b[32m2\u001b[0m\n", "\u001b[36mx\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"One\"\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 该情况使用scalac编译将报错\n", "val i = 1\n", "val Two = 2 // added\n", "val x = (i: @switch) match {\n", " case 1 => \"One\"\n", " case Two => \"Two\" // replaced the '2'\n", " case _ => \"Other\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](images/switch_anno.jpg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scala使用tableswitch优化具有一定争议,不想@tailrec那么通用。tableswitch优化需要满足几个条件:\n", "- 匹配的值必须是已知的整数\n", "- 匹配表达式必须是简单表达式,不能有任何类型检查、if语句、抽取器等\n", "- 表达式必须在编译时有可用的值\n", "- 表达式包含多余两个case的情况,不然优化反而更慢" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.2 使用Map代替switch" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "April\n" ] }, { "data": { "text/plain": [ "\u001b[36mmonthNumberToName\u001b[0m: \u001b[32mMap\u001b[0m[\u001b[32mInt\u001b[0m, \u001b[32mString\u001b[0m] = \u001b[33mMap\u001b[0m(\n", " \u001b[32m5\u001b[0m -> \u001b[32m\"May\"\u001b[0m,\n", " \u001b[32m10\u001b[0m -> \u001b[32m\"October\"\u001b[0m,\n", " \u001b[32m1\u001b[0m -> \u001b[32m\"January\"\u001b[0m,\n", " \u001b[32m6\u001b[0m -> \u001b[32m\"June\"\u001b[0m,\n", " \u001b[32m9\u001b[0m -> \u001b[32m\"September\"\u001b[0m,\n", " \u001b[32m2\u001b[0m -> \u001b[32m\"February\"\u001b[0m,\n", " \u001b[32m12\u001b[0m -> \u001b[32m\"December\"\u001b[0m,\n", " \u001b[32m7\u001b[0m -> \u001b[32m\"July\"\u001b[0m,\n", " \u001b[32m3\u001b[0m -> \u001b[32m\"March\"\u001b[0m,\n", " \u001b[32m11\u001b[0m -> \u001b[32m\"November\"\u001b[0m,\n", " \u001b[32m8\u001b[0m -> \u001b[32m\"August\"\u001b[0m,\n", " \u001b[32m4\u001b[0m -> \u001b[32m\"April\"\u001b[0m\n", ")\n", "\u001b[36mmonthName\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"April\"\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// 有时,我们也可以使用Map来代替简单的switch\n", "val monthNumberToName = Map(\n", " 1 -> \"January\",\n", " 2 -> \"February\",\n", " 3 -> \"March\",\n", " 4 -> \"April\",\n", " 5 -> \"May\",\n", " 6 -> \"June\",\n", " 7 -> \"July\",\n", " 8 -> \"August\",\n", " 9 -> \"September\",\n", " 10 -> \"October\",\n", " 11 -> \"November\",\n", " 12 -> \"December\"\n", ")\n", "\n", "val monthName = monthNumberToName(4)\n", "println(monthName) // prints \"April\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.3 匹配变量\n", "在被匹配或提取的值中,编译器假定以大写字符开头的为类型名,以小写字母开头的为变量名。" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mcheckY\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def checkY(y: Int) = {\n", " for {\n", " x <- Seq(99, 100, 101)\n", " } {\n", " val str = x match {\n", " case `y` => \"found y!\"\n", " case i: Int => \"int: \" + i\n", " }\n", " println(str)\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "int: 99\n", "found y!\n", "int: 101\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "checkY(100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在case子句中,以小写字母开头的标识符被认为是用来提取待匹配的新变量。**如果需要引用之前已经定义的变量时,应使用反引号将其包围。**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.4 使用逻辑或来匹配多个case" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait \u001b[36mCommand\u001b[0m\n", "defined \u001b[32mobject \u001b[36mStart\u001b[0m\n", "defined \u001b[32mobject \u001b[36mGo\u001b[0m\n", "defined \u001b[32mobject \u001b[36mStop\u001b[0m\n", "defined \u001b[32mobject \u001b[36mWhoa\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "trait Command\n", "case object Start extends Command\n", "case object Go extends Command\n", "case object Stop extends Command\n", "case object Whoa extends Command" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mexecuteCommand\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def executeCommand(cmd: Command) = cmd match {\n", " case Start | Go => \"start\" // or use start()\n", " case Stop | Whoa => \"stop\"\n", "}" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres11\u001b[0m: \u001b[32mString\u001b[0m = \u001b[32m\"stop\"\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "executeCommand(Stop)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 不同内容的匹配\n", "除了上面对整数、字符串等对象的匹配之外,模式匹配还可以对序列、元组、样本类(case class)、映射等进行匹配。" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass \u001b[36mPerson\u001b[0m\n", "defined \u001b[32mclass \u001b[36mDog\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "case class Person(firstName: String, lastName: String)\n", "case class Dog(name: String)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction \u001b[36mechoWhatYouGaveMe\u001b[0m" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def echoWhatYouGaveMe(x: Any): String = x match {\n", " // constant patterns\n", " case 0 => \"zero\"\n", " case true => \"true\"\n", " case \"hello\" => \"you said 'hello'\"\n", " case Nil => \"an empty List\"\n", " \n", " // sequence patterns\n", " case List(0, _, _) => \"a three-element list with 0 as the first element\"\n", " case List(1, _*) => \"a list beginning with 1, having any number of elements\"\n", " case Vector(1, _*) => \"a vector starting with 1, having any number of elements\"\n", " \n", " // tuples\n", " case (a, b) => s\"got $a and $b\"\n", " case (a, b, c) => s\"got $a, $b, and $c\"\n", " \n", " // constructor patterns\n", " case Person(first, \"Alexander\") => s\"found an Alexander, first name = $first\"\n", " case Dog(\"Suka\") => \"found a dog named Suka\"\n", " \n", " // typed patterns\n", " case s: String => s\"you gave me this string: $s\"\n", " case i: Int => s\"thanks for the int: $i\"\n", " case f: Float => s\"thanks for the float: $f\"\n", " case a: Array[Int] => s\"an array of int: ${a.mkString(\",\")}\"\n", " case as: Array[String] => s\"an array of strings: ${as.mkString(\",\")}\"\n", " case d: Dog => s\"dog: ${d.name}\"\n", " case list: List[_] => s\"thanks for the List: $list\"\n", " case m: Map[_, _] => m.toString\n", " \n", " // the default wildcard pattern\n", " case _ => \"Unknown\"\n", "}" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zero\n", "true\n", "you said 'hello'\n", "an empty List\n", "a three-element list with 0 as the first element\n", "a list beginning with 1, having any number of elements\n", "a list beginning with 1, having any number of elements\n", "a vector starting with 1, having any number of elements\n", "got 1 and 2\n", "got 1, 2, and 3\n", "found an Alexander, first name = Melissa\n", "found a dog named Suka\n", "you gave me this string: Hello, world\n", "thanks for the int: 42\n", "thanks for the float: 42.0\n", "an array of int: 1,2,3\n", "an array of strings: coffee,apple pie\n", "dog: Fido\n", "thanks for the List: List(apple, banana)\n", "Map(1 -> Al, 2 -> Alexander)\n", "you gave me this string: 33d\n" ] }, { "data": { "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// trigger the constant patterns\n", "println(echoWhatYouGaveMe(0))\n", "println(echoWhatYouGaveMe(true))\n", "println(echoWhatYouGaveMe(\"hello\"))\n", "println(echoWhatYouGaveMe(Nil))\n", "// trigger the sequence patterns\n", "println(echoWhatYouGaveMe(List(0,1,2)))\n", "println(echoWhatYouGaveMe(List(1,2)))\n", "println(echoWhatYouGaveMe(List(1,2,3)))\n", "println(echoWhatYouGaveMe(Vector(1,2,3)))\n", "// trigger the tuple patterns\n", "println(echoWhatYouGaveMe((1,2))) // two element tuple\n", "println(echoWhatYouGaveMe((1,2,3))) // three element tuple\n", "// trigger the constructor patterns\n", "println(echoWhatYouGaveMe(Person(\"Melissa\", \"Alexander\")))\n", "println(echoWhatYouGaveMe(Dog(\"Suka\")))\n", "// trigger the typed patterns\n", "println(echoWhatYouGaveMe(\"Hello, world\"))\n", "println(echoWhatYouGaveMe(42))\n", "println(echoWhatYouGaveMe(42F))\n", "println(echoWhatYouGaveMe(Array(1,2,3)))\n", "println(echoWhatYouGaveMe(Array(\"coffee\", \"apple pie\")))\n", "println(echoWhatYouGaveMe(Dog(\"Fido\")))\n", "println(echoWhatYouGaveMe(List(\"apple\", \"banana\")))\n", "println(echoWhatYouGaveMe(Map(1->\"Al\", 2->\"Alexander\")))\n", "// trigger the wildcard pattern\n", "println(echoWhatYouGaveMe(\"33d\"))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. case语句的变量绑定" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. 抽取器" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. 正则表达式的匹配" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Scala 2.11", "language": "scala211", "name": "scala211" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala211", "pygments_lexer": "scala", "version": "2.11.6" } }, "nbformat": 4, "nbformat_minor": 0 }