Scala编程Chapter24: 抽取器

来源:互联网 发布:oracle 造12条数据 编辑:程序博客网 时间:2024/05/21 10:12
package chapter24/** * @author xuyiqiang *//** * 抽取器:Extractors * #相关章节:chapter15:类型擦除的唯一例外是数组,因为它们在Scala和java里被特殊处理了。# * 我们通常用模式匹配来做数据的解构和分析,本节将更深层次的推广这个概念 * 目前为止,构造器模式是和样本类相关联的。 * 有时候你希望在不创建关联的样本类的前提下编写出类似的模式,甚至创建出属于自己的模式类型,抽取器便是为此而生。 * 本节解释了抽取器的概念,以及如何用来定义与对象表达解耦的模式。 * *//** * 24.1 例子:抽取email地址 */abstract class EMail {  def isEmail(s: String): Boolean  def domain(s: String): String  def user(s: String): String  val s = ""  if (isEmail(s)) println(user(s) + " AT " + domain(s))  else println("not a email address")  //这种做法可行,但是有点笨手笨脚。而且要把若干这样的检查组合在一起会使得事情变得过于复杂。  //比如 想要在列表中找到连续的两条相同用户的email地址字符串,如果用这种方式会变得异常麻烦  //模式匹配是这种问题的理想工具,让我们假设你可以使用以下模式做字符串匹配  //Email(user,domain)  //匹配成功的条件是字符串包含内嵌的"at"符号(@)  //这种情况下它把user变量绑定到字符串@符号之前的部分,并把domain变量绑定到之后的部分  //进而有:  s match {    //    case Email(user, domain) => println(user +" AT "+ domain)    case _ => println("not a email address")  }  //找到两个连续的同一用户的email地址  s match {    //    case EMail(u1, d1) :: EMail(u2, d2) :: _ if(u1  == u2) =>     case _ =>  }  //上面的做法远比使用访问函数写出的任何东西都更为易读。然而问题在于字符串不是样本类,  //他们不具有符合于EMail(user, domain)这样的表达方式。  //这也是scala抽取器的由来:##它们可以让你为已经存在的类型定义新模式,而这种模式不需要遵守类型的内部表达式##}/** * 24.2 抽取器 :Scala里的抽取器是具有名为unapply或unapplySeq成员方法的对象。 *///unapply方法的目的是为了匹配并分解值//通常抽取器对象还会定义可以构建值的对偶方法apply,但并非必须。object EMail extends ((String, String) => String) {  /**   * 这里对象声明中的"(String, String) => String"与Function2[String, String, String]同义,   * 是对EMail实现的抽象apply方法的声明   */  //注入方法(可选)  def apply(user: String, domain: String) = user + "@" + domain  //抽取方法(规定的)  def unapply(str: String): Option[(String, String)] = {    val parts = str split "@"    if (parts.length == 2) Some(parts(0), parts(1)) else None  }}//apply方法的含义始终不变,它使得EMail对象可以直接使用括号应用方法的参数,//所以可以写出EMail("John","qq.com")构造字符串"John@qq.com"//unapply方法是EMail成为抽取器的原因。从某种意义上讲,它逆转了apply的构建进程。//apply是将传入字符串拼成email地址字符串并返回,而unapply则是传入email地址字符串 并返回可能存在的两个字符串:地址的用户和域。//但unapply必须还能处理传入字符串并非email地址的情况,这也是unapply返回Option类型的字符串对偶的原因。object Test extends App {  val selectorString = "1821340314@qq.com"  selectorString match {    case EMail(user, domain) => println(user + ":" + domain)    case _                   => println("else")  }  //说明:# EMail(user, domain)不是调用了apply方法 #  //一旦模式匹配碰到的是抽取器对象所指的模式,它就会在选择器表达式中调用抽取器的unapply方法,如下  EMail.unapply(selectorString)  //如之前所见,对EMail.unapply的调用返回的或是None,或是Some(u, d).对于Some类型值来说,  //u是地址的用户部分,d是域部分;如果是None,则说明模式不能匹配,然后系统会尝试其他的模式或失败返回MatchError异常。  val user = "xuyiqiang"  val domain = "163.com"  val result = EMail.unapply(EMail.apply(user, domain))  println(result)  EMail.unapply(selectorString) match {    case Some((u, d)) => println(EMail.apply(u, d))    case _            => println("else")  }}/** * 24.3 0或1个变量的模式(抽取器绑定一个变量或不绑定变量情况) *///前面例子中的unapply方法在模式匹配成功的情况下返回了一对元素值。这可以很方便的推广到 超过两个变量的模式上。 //要绑定N个变量,unapply应该返回N元的元组,并包括在Some中。//不过,模式仅绑定一个变量的情况需要特别对待。Scala中没有一元的元组。//要返回唯一的模式元素,unapply方法会简单地把它包装在Some中object Twice {  def apply(s: String): String = s + s  def unapply(s: String): Option[String] = {    val length = s.length / 2    val half = s.substring(0, length)    if (half == s.substring(length)) Some(half) else None  }}//还有一种可能是抽取器模式不绑定任何变量。这种情况下它的unapply方法返回布尔值——true或false说明匹配成功或失败。object UpperCase {  def unapply(s: String): Boolean = s.toUpperCase == s}//userTwiceUpper函数的模式匹配代码集中应用了所有之前定义的抽取器、object Test1 extends App {  def userTwiceUpper(s: String) = s match {    case EMail(Twice(x @ UpperCase()), domain) => println("match: " + x + " in domain " + domain)    case _                                     => println("no match")  }  //函数第一个模式匹配的字符串是email地址,并且用户部分需要由大写字母形式的相同字符串出现两次组成。  userTwiceUpper("xyqxyq@162.com") //no match  userTwiceUpper("XYSXYS@162.com") //match: XYS in domain 162.com  //注意函数userTwiceUpper功能里的UpperCase带着空参数列表。这里的()是不能被省略的,否则会执行与UpperCase对象的匹配检查!}/** * 24.4 变参抽取器(unapplySeq) *///前面的email地址抽取方法返回的都是固定数目的元素值,这有些时候不够灵活。/*  dom match{    case Domain("org", "acm") => println("acm.org")    case Domain("com", "sun", "java") => println("java.sun.com")    case Domain("net", _*) => println("a.net domain")  }*///剩下的问题就是抽取器如何支持像之前例子里的那种变参匹配,让模式中可以含有可变数量的子模式。//目前所碰到到的unapply方法并不足以解决这个问题,因为它们每次成功匹配返回的都是固定数目的子元素。//为了处理这一情况,Scala允许定义特别为变参匹配准备的不同的抽取方法。被称为unapplySeq.object Domain {  //注入方法(可选)  def apply(parts: String*): String = parts.reverse.mkString(".")  //抽取方法(不可选)  def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse)}//Domain对象定义的unapplySeq方法,首先会把字符串以句点为分隔做拆分。具体是通过java的split方法,以正则表达式为参数实现。//split的结果是一个字符串数组。然后unapplySeq会把数组所有元素倒置并包装在Some中返回。 object Test2 extends App {  println(Domain("abcde", "123"))  def isTomInDotCom(s: String): Boolean = s match {    case EMail("tom", Domain("com", _*)) => true    case _                               => false  }  println(isTomInDotCom("tom@sun.com"))  println(isTomInDotCom("peter@sun.com"))  println(isTomInDotCom("tom@acm.org"))}object ExpandedEMail {  def unapplySeq(email: String): Option[(String, Seq[String])] = {    val parts = email split "@"    if (parts.length == 2) Some(parts(0), parts(1).split("\\.").reverse) else None  }}object Test3 extends App {  val s = "tom@support.epfl.ch"  val ExpandedEMail(name, topdom, subdoms @ _*) = s  println("end")  //name=tom  topdom=ch  subdoms=WrappedArray(epfl, support)  List}/** * 24.5 抽取器和序列模式 *///之前你已经知道可以用序列模式访问列表或数组的元素://List()//List(x, y, _*)//Array(x, 0, 0, _)//实际上,这些序列模式都是使用标准Scala库的抽取器实现的。//例如,List(...)形式的模式是由于Scala.List的半生对象定义了unapplySeq方法的抽取器。/** * 24.6 抽取器 VS 样本类 *///尽管样本类非常有用但是它暴露了数据的具体表达方式。这是指构造器模式中的类名与选择器对象的具体表达类型是一致的。//抽取器截断了数据表达与模式之间的联系。表征独立是抽取器对样本类的一大优势。//那么对你的模式匹配来说应该采用这两种方法的哪一种? 这要分情况讨论。/** * 24.7 正则表达式 *///正则表达式是非常能够体现抽取器价值的应用领域。类似于java,Scala通过库提供了正则表达式,但抽取器使它能更能好地执行交互操作。object Test4 extends App {  import scala.util.matching.Regex  /**   * (1)形成正则表达式   */  //Scala的正则表达式类放在scala.util.matching包里。import scala.util.matching.Regex  val Decimal = new Regex("""(-)?(\d+)(\.\d*)?""")  val Decimal1 = """(-)?(\d+)(\.\d*)?""".r  /**   * (2)用正则表达式做查找   */  //<1>查找字符串str中符合正则表达式regex的首次出现,返回Option类型的结果。  //regex findFirstIn str  //<2>查找字符串str中正则表达式regex的所有出现,返回Iterator结果。  //regex findAllIn str  //<3>查找字符串str开始位置正则表达式regex的出现。返回Option类型的结果。  //regex findPrefixofv str  val input = "for -1.0 to 99 by 3"  for (s <- Decimal findAllIn input) println(s)  println(Decimal findFirstIn input)  println(Decimal findPrefixOf input)  //-1.0  //99  //3  //Some(-1.0)  //None  /**   * (3)用正则表达式是抽取值   */  //Scala中所有的正则表达式都定义了抽取器,这可以用来鉴别匹配于正则表达式"#分组#"的子字符串。  val Decimal(sign, integerpart, decimalpart) = "-1.23"  println(sign)  println(integerpart)  println(decimalpart)  //-  //1  //.23}/** * 24.8 小结 * 本章用抽取器推广模式匹配的方式。抽取器可以让你定义模式类型,而不需要与选择的表达式类型相关联。它能让匹配模式的类型更为灵活。 * 抽取器是能够让你定义更有弹性的库抽象的又一把工具。举例来说,它们被大量地使用在Scala库中,以方便正则表达式匹配的进行。 */

1 0
原创粉丝点击