返回介绍

数学基础

统计学习

深度学习

工具

Scala

四、一切皆模式

发布于 2023-07-17 23:38:22 字数 7036 浏览 0 评论 0 收藏 0

  1. Scala 中,很多地方都允许使用模式,而不仅仅是 match 表达式。

4.1 变量定义中的模式

  1. 当定义一个val 或者var 时,都可以用模式而不是简单的标识符。如:可以将一个元组解开,并将其中每个元素分别赋值给不同的变量。

    
    
    xxxxxxxxxx
    val tuple = (123,"abc") val list = List(456,789) val (num1,str1) = tuple // 解包(必须包含圆括号,因为圆括号表示元组) val List(num2,num3) = list // 解包 println("num1:"+num1+";\tstr1:"+str1+";\tnum2:"+num2+";\tnum3:"+num3) // 打印: num1:123; str1:abc; num2:456; num3:789
  2. 这种语法结构在处理样例类时非常有用。当你知道要处理的样例类是什么时,就可以用一个模式来解析它。

    
    
    xxxxxxxxxx
    case class Worker(name:String) case class System(worker:Worker,name:String) ​ val system = System(Worker("worker_1"),"system_1") val System(unpack_worker,system_name) = system // 解包 ​ println("unpack_worker:"+unpack_worker+"; system_name:"+system_name) // 打印: unpack_worker:Worker(worker_1); system_name:system_1

4.2 case 序列

  1. 用花括号包起来的一系列case (即可选分支)可以用在任何允许出现函数字面量的地方。

    本质上讲,case 序列就是一个函数字面量,只是更加通用。不像普通函数那样只有一个入口和参数列表,case 序列可以有多个入口,每个入口都有自己的参数列表。每个case 对应该函数的一个入口,该入口的参数列表通过模式来指定。每个入口的逻辑主体是case 右边的部分。

    
    
    xxxxxxxxxx
    val func: Any => Unit = { case (i,j) => println("catch tuple:("+i+","+j+")") case s:String => println("catch string:"+s) case Some(x) => println("catch Opiton:"+x) case e => println("catch other:"+e) } ​ func(Tuple2("hello",1)) // 匹配第一个 case: i="hello",j=1 func("hello",2) // 匹配第一个 case: i="hello",j=2 func("hello",2,0.0) // 匹配第四个 case: e=("hello",2,0.0) func("A String") // 匹配第二个 case: s="A String" func(Some(0.0)) // 匹配第三个 case: x=0.0 func(Map("a"->1)) // 匹配第四个 case: e=Map("a"->1)
  2. 通过 case 序列得到的是一个偏函数。如果我们将这样一个函数应用到它不支持的值上,则会产生一个运行时异常。

    
    
    xxxxxxxxxx
    val second_val: List[Int] => Int = { case x :: y :: _ => y } println(second_val(List(1,2,3))) // 打印: 2 println(second_val(List())) // 运行时异常:抛出MatchError

    编译时,编译器会警告:

    
    
    xxxxxxxxxx
    Warning:(1, 44) match may not be exhaustive. It would fail on the following inputs: List(_), Nil val second_val: List[Int] => Int = {
  3. 如果希望检查某个偏函数是否对某个入参有定义,则必须首先告诉编译器:你知道你要处理的是偏函数。但是List[Int] => Int 这个类型涵盖了所有从List[Int]Int 的函数,不论这个函数是偏函数还是全函数。

    仅仅涵盖从List[Int]Int 的偏函数的类型写作 PartialFunction[List[Int],Int] 。偏函数定义了一个isDefinedAt 方法,用于检查该函数是否对某个特定的参数值有定义。

    
    
    xxxxxxxxxx
    val second_val: PartialFunction[List[Int], Int] = { case x :: y :: _ => y } println(second_val.isDefinedAt(List())) // 打印: false println(second_val.isDefinedAt(List(1))) // 打印: false println(second_val.isDefinedAt(List(1,2))) // 打印: true println(second_val.isDefinedAt(List(1,2,3))) // 打印: true
  4. 偏函数的典型应用场景是模式匹配函数字面量,如这里的示例。事实上,这样的表达式会被Scala 编译器翻译成偏函数。这样的翻译发生了两次:一次是实现真正的函数,另一次是测试这个函数是否对指定参数值有定义。

    如,函数字面量{ case x :: y :: _ => y} 被翻译为如下的值偏函数:

    
    
    xxxxxxxxxx
    new PartialFunction[List[Int],Int]{ def apply(xs:List[Int]) = xs match{ case x :: y :: _ => y } def isDefinedAt(xs:List[Int]) = xs match { case x :: y :: _ => true case _ => false } }
    • 如果函数字面量声明的类型是PartialFunction,则这样的翻译就会生效。
    • 如果声明的类型是Function1,或者没有声明,则函数字面量对应的就是一个全函数。
  5. 一般来说推荐使用全函数,因为偏函数允许运行时出现错误,而这个错误编译器无法帮助我们。

    不过有时候偏函数也特别有用。比如:你确信不会有无法处理的值传入。也有的框架可能会用到偏函数,每次函数调用前都会用 isDefinedAt 做一次检查。

4.3 for 表达式中的模式

  1. 可以在for 表达式中使用模式。

    
    
    xxxxxxxxxx
    for ((name,age) <- Map("zhang san" -> 20,"li si" -> 23)) println("name:"+name+";age:"+age) ​ /* 输出: name:zhang san;age:20 name:li si;age:23 */

    这个 Map 每次迭代时交出一个对偶,然后该对偶与 (name,age) 进行匹配。这个匹配永远是成功的。

  2. 也有匹配失败的情况,迭代中无法匹配的值会被直接丢弃。

    
    
    xxxxxxxxxx
    for(Some(name) <- List(Some("zhang san"),Some("li si"),None)) println("name:"+name) ​ /* 输出: name:zhang san name:li si */

    由于 None 无法匹配 Some(name),因此None 不会出现在输出中。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文