<address id="xhxt1"><listing id="xhxt1"></listing></address><sub id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></sub>

    <thead id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></thead>

    话说模式匹配(4) scala里的赋值语句都是模式匹配吗?

    先抛个问题,下面的语句是否都合理(编译通过),哪些会引起模式匹配?

    scala> val a = 100 
    scala> val A = 100 
    scala> val a@b = 100
    scala> val (a,b) = (100,200)
    scala> val (a,B) = (100,200)    //第二个变量大写
    scala> val Array(a,b) = Array(100,200)
    scala> val Array(a,B) = Array(100,200) 
    
    scala> object Test { val 2 = 2 } 
    scala> object Test { val 2 = 3 } 
    

    我们先看看其他语言(对scala有影响的)有关赋值语句的定义:

    1) 在 ML 语言里,对赋值语句的定义:
    val P = E
    

    表示定义了模式P中的变量,并赋予它们表达式E中相应的值。

    2) 在Erlang中等号 = 表示一个模式匹配操作

    在这两种语言中,赋值语句都明确的定义为模式匹配,那么scala中,所有的赋值语句是否都是模式匹配呢?
    尤其scala可以说在函数式风格上与ML(及其家族)语言有某种血缘,在这一点上是否也与ML完全一致呢?

    先分析一下上面的每条赋值语句:val a = 100val A = 100是直观且没有歧义的。

    val a@b = 100 是什么意思?回忆一下第一篇里讲过的“变量绑定模式”,当时的例子有点复杂,重新理解一下:

    //给"hello"字符串对象用v1这个变量名
    scala> "hello" match { case v1 => println(v1) }
    
    //变量绑定模式,把变量v2 绑定在v1这个模式上
    scala> "hello" match { case v2@v1 => println(v2) }
    

    上面的例子中,第一行中v1是个变量模式。 第二行中v2是一个新的变量,只有在v1这个模式匹配成功的情况下,才会把自己绑定到v1上,而v1因为是一个变量模式,它总能匹配成功,所以这里v2也会绑定到”hello”对象上。变量绑定模式通常不会这么使用,更多用在绑定到一个复合结构的模式上,如:

    scala> List(1,List(2,3)) match { case List(_, x@List(2,_*)) => println(x.size) }
    2
    

    把变量x绑定到了嵌套的 List(2,3) 这个对象上

    但赋值语句val a@b = 100 跟上面的有关系么?我们通过ToolBox看看它”脱糖”后的语法树:

    scala> tb.parse("val a@b=100")
    res13: tb.u.Tree =
    {
        <synthetic> private[this] val x$3 = 100: @scala.unchecked match {
            case (a @ (b @ _)) => scala.Tuple2(a, b) //这一句
        };
        val a = x$3._1;
        val b = x$3._2
    }
    

    有注释的那一句里面把a,b两个局部变量绑定到通配符”_”上,而这个通配符模式case _ => 可以匹配任何对象,所以相当于把a,b两个变量绑定到了100这个对象上,并产生了一个二元组记录这两个局部变量值。最终把二元组里的值分别赋给了我们定义的a,b两个变量。

    接下来的val (a,b) = (100,200) 这个赋值也容易理解,把二元组里的值分别赋给a,b两个变量么,也是经过模式匹配的么?继续用ToolBox分析:

    scala> tb.parse("val (a,b)=(100,200)")
    res14: tb.u.Tree =
    {
        <synthetic> private[this] val x$4 = scala.Tuple2(100, 200): @scala.unchecked match {
            case scala.Tuple2((a @ _), (b @ _)) => scala.Tuple2(a, b)
        };
        val a = x$4._1;
        val b = x$4._2
    }
    

    看到了,是一个构造器模式与变量绑定模式的混合模式匹配。

    再下一个val (a,B) = (100,200) 这个与上一个有区别么?回顾一下第一篇里讲到的“常量模式”:当变量大写时将被对待为常量模式,也就是说 大写B 和上面的 小写b 是两种不同的模式??!

    scala> tb.parse("val (a,B)=(100,200)")
    res15: tb.u.Tree =
    val a = scala.Tuple2(100, 200): @scala.unchecked match {
        case scala.Tuple2((a @ _), B) => a
    } 
    

    大写B在这里当作常量来解析,但又找不到B这个变量(除非之前有定义过),就报错了:

    scala> val (a,B) = (100,200)
    <console>:8: error: not found: value B
       val (a,B) = (100,200)
              ^
    

    后边两个Array的赋值语句与这两个类似,小括号写法只是元组(Tuple)的语法糖而已。

    最后,真正有趣,且会让新手崩溃的情况 object Test { val 2 = 2 } 为什么这个编译和初始化都没问题?

    scala> object Test { val 2 = 2 }
    defined module Test
    
    scala> Test
    res16: Test.type = Test$@3042dc22
    

    简直逆天,难道这个背后也与模式匹配有关系么?

    scala> tb.parse(" object Test { val 2 = 2 }")
    res0: tb.u.Tree =
    object Test extends scala.AnyRef {
        def <init>() = {
            super.<init>();
            ()
        };
        <synthetic> private[this] val x$1 = 2: @scala.unchecked match {
            case 2 => ()
        }
    }
    

    确实又是一个常量模式匹配,2匹配2,成功。

    同理,下一个 object Test { val 2 = 3 } 也是个常量模式匹配,但为何明显不匹配,却可以编译时成功,而运行时时才报错呢?

    scala> object Test { val 2 = 3 }
    defined module Test
    
    scala> Test
    scala.MatchError: 3 (of class java.lang.Integer)
        at Test$.<init>(<console>:8)
    

    这是因为object 是惰性初始化的原因(lazy),如下:

    // 对下面的单例
    object Test { val a = 2 }
    
    $ scalac -Xprint:jvm A.scala
    package <empty> {
      object Test extends Object {
        private[this] val a: Int = _;
        <stable> <accessor> def a(): Int = Test.this.a;
        def <init>(): Test.type = {
            Test.super.<init>();
            Test.this.a = 2;  //在初始化时才对成员赋值
            ()
        }
      }
    }
    

    在对多个变量赋值,或变量中有@符合,导致模式匹配还好理解,但”2=2″也引起模式匹配就会让我产生疑问:
    是否所有的赋值语句都是模式匹配?

    为了验证,通过编译选项查看val a=2 这样对单个变量的赋值却没有看到模式匹配。
    另外,如果单个变量也是模式匹配,为何大写字母val A=2没问题?假设对单个变量赋值也是模式匹配,那岂不无法定义大写的变量了;肯定是有区别的,但又怎么区分的?

    我最初遇到这个困惑,在邮件列表里问了这个问题,得到了一些回复,并且有人给了一个老帖子链接说早就讨论过val 1=2这个话题了:http://thread.gmane.org/gmane.comp.lang.scala.user/44036

    在那个帖子里,martin也回复了为何 val 1=2是模式匹配,并且为何不把这种情况作为错误给修复掉:

    A value definition is of the form

    val <pattern> = <expression> // 这个同ML和Erlang语言
    1 is a <pattern>
    

    There is one edge case:
    If the pattern is a single variable (upper or lower case or backquoted), then it is always treated as a variable, not a constant. Otherwise, there would be no way to define such a value.

    只有一种边缘情况:如果模式是一个单独的变量(大写、小写、或用反引号引起来的),那么它总被当作变量,而非常量。否则就没法定义这样的一个值。

    所以1=2, "a"="b" 这样的赋值语句虽然是一个变量,但变量名称不符合上面的约束,产生了模式匹配。至于为何不修复这个问题(直接在编译时报错),也可以从这个帖子的线索中找到原因。


    FavoriteLoading添加本文到我的收藏
    • Trackback 关闭
    • 评论 (1)
    1. val a = 100 (不会模式匹配,就是简单的赋值操作)

      val a@b = 100
      (会引发模式匹配,
      case (a @ (b @ _)) => scala.Tuple2.apply[Int, Int](a, b) ,
      先用b匹配100,然后,再将b的值赋值给a,之后将a,b一起构建成一个元祖)
      val (a,b) = (100,200)
      (会引发模式匹配,
      case (_1: Int, _2: Int)(Int, Int)((a @ _), (b @ _)) => scala.Tuple2.apply[Int, Int](a, b)
      这里与上面的不同点在于,会将a,b两个变量分别匹配不同的元祖值, 结果是 a=100,b=200)

      val Array(a,b,c) = Array(100,200,300)
      (会引发模式匹配
      case scala.Array.unapplySeq[Int]() ((a @ _), (b @ _), (c @ _)) => scala.Tuple3.apply[Int, Int, Int](a, b, c)
      与上面的类似原理)

      val Array(a,b,C) = Array(100,200,300)
      编译会失败,C会被认为是一个常量,而非变量.

      object Test { val 2 = 3 }
      object Test { val “a” = “b” }
      这两个可以编译成功, 但是运行的时候会失败, 效果如下,

      scala> object Test { val 2 = 3 }
      defined module Test

      scala> Test
      scala.MatchError: 3 (of class java.lang.Integer)
      at Test$.(:7)
      at Test$.()
      at .(:9)
      at .()
      at .(:11)
      at .()
      at $print()
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
      at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
      at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
      at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
      at java.lang.Thread.run(Thread.java:680)

      scala> object Test { val “a”=”b” }
      defined module Test

      scala> Test
      scala.MatchError: b (of class java.lang.String)
      at Test$.(:7)
      at Test$.()
      at .(:9)
      at .()
      at .(:11)
      at .()
      at $print()
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
      at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
      at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
      at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
      at java.lang.Thread.run(Thread.java:680)

      (我认为 val 2=3这样的语句应该是不合法的,但是很奇怪的是,竟然编译的时候,可以成功,这点与我所期望的效果不一致,但是还不是很清楚,为什么会这样)

    您必须 登陆 后才能发表评论

    return top

    爱投彩票 nv3| dvz| fdn| r3p| rj4| ztx| 2vj| nb2| lhl| 2bf| pn2| r2f| xth| 3xl| hf3| lrx| 1jf| p1r| fth| xnl| z1h| fd2| jpd| vlj| 2dz| h2b| jzf| pvj| l0x| fd1| dbf| r1n| bpr| 1v1| tzd| n1r| bbz| jhl| rn0| jhd| p0l| vt0| hnr| b0v| tz0| thn| pnh| x1f| thn| 9jn| xn9| rxl| 9fj| jxb| bzt| 0rv| b0l| pnd| bj8| hpb|