引言
最近抱着《Programming in Scala》(英文第二版)在死啃Scala。在阅读第26章Extractor时,偶然在Stack Overflow上搜到一个帖子《Scala pattern matching variable binding》。其中Daniel C. Sobral的回答,让我产生了一系列的思考,深感其一语道破天机、顿觉醍醐灌顶,故得此文。
问题
Zapadlo问道:为什么这样会在第一行case出错:IsUpperCase错误的参数数量?
object IsUpperCase {
def unapply(s: String): Option[String] = {
if (s.toUpperCase() == s) {
Some(s)
} else {
None
}
}
}
val s = "DuDu@qwadasd.ru"
s match {
case u @ IsUpperCase() => println("gotcha!")
case _ =>
}
而换成这样却又可以?
case IsUpperCase(u) => println("gotcha!")
答案
Daniel回答道:
A pattern match works the opposite of a function call. Where X(a, b) pass parameter "a" and "b" to "X" outside a pattern match, in a pattern match "a" and "b" are returned from "X".
意思指:
运用模式匹配的过程,其实是函数调用的逆过程。函数调用的过程,是给定参数a和b,然后得到x;而模式匹配,则是根据x,逆向得到a和b的过程。
并由此给出修改意见:在使用变量绑定方式进行模式匹配时,给IsUpperCase()
一个占位符用作其参数
case u @ IsUpperCase(_) => println("gotcha!")
思考
回想书中关于Extractor中apply()
与unapply()
的关系:『前者是注入器,利用给定参数得到某个设定集合里的一个元素;后者则是把给定的元素分解为若干个组成部分。』于是,将Daniel答复中的function call换成apply,将pattern match用unapply代替之,也是可行的。
那么,apply的输入参数将是unapply的输出,反之亦然。Extractor的实质,就是利用unapply去还原可能的apply的参数。所以在模式匹配时,我们需要用变量名或者占位符_
去绑定解析的结果,为匹配结果留出相应的坑。
反观上例,unapply()
的输出是Option[String]
,那说明对应的必定是apply(s:String)
,所以在模式匹配时,我们需要给IsUpperCase()
一个变量来接收匹配的结果。这就是为什么Zapadlo采取第二种方式可行的原因。
再来想想为什么采用『变量绑定』的方式时,需要使用占位符_
?这是因为变量绑定,可以说是Scala为模式匹配中标准的变量匹配模式提供的一个语法糖。u @ IsUpperCase(_)
这样的写法,相当于先匹配@后面的部分,然后把匹配的结果赋值给变量u
。而从前面的分析得出,IsUpperCase需要至少一个参数,所以我们用占位符代替了这个参数。
最后,再想想为什么把Extractor中unapply的返回值改为Boolean
后又可以了呢?这是因为,这时候apply是纯粹多余的、没有意义的,Extractor并不需要承担构造的任务,不需要绑定到某个或者某些变量上,它只是做个简单的判断。所以,在书中的第636页专门描述了这种情形,这也是Scala语言规范的一部分:
It’s also possible that an extractor pattern does not bind any variables. In that case the corresponding unapply method returns a boolean—true for success and false for failure.
这种情况下,类似这样的Extractor是没有办法象下面这个Email一样,利用匹配进行对象还原的。从另一个角度,其实我们也想得通,IsUpperCase.apply(true)
能得到某个特定的字符串吗?
Email.unapply(obj) match {
case Some(user, domain) => Email.apply(user, domain)
}
理解了这些,理解Extractor多个变量时就会感觉容易多了,不外乎解析得到类似列表或者String*
这样未知参数个数的结果了。
结语
Scala是一门夹杂了函数式编程的面向对象编程语言。在学习的过程中,val、equals与==、eq与ne,还有著名的akka框架等等等等,无一不让我心潮澎湃,让我隐约感受到了应用Scala实践DDD时将获得的那种顺畅感。希望能尽早完成基础语法的学习,再小试牛刀,哈哈。