• 赋值表达式的值


    CoffeeScript有个语法叫解构赋值(Destructuring Assignment),可以将一个对象的不同成员一次性赋值给多个的变量。官网中给了下面一个例子:

    futurists =
      sculptor: "Umberto Boccioni"
      painter:  "Vladimir Burliuk"
      poet:
        name:   "F.T. Marinetti"
        address: [
          "Via Roma 42R"
          "Bellagio, Italy 22021"
        ]
    {poet: {name, address: [street, city]}} = futurists
    alert name + " — " + street
    

    运行结果自然是 "F.T. Marinetti — Via Roma 42R",因为coffee将其翻译为下面的JS:

    
    var city, futurists, name, street, _ref, _ref1;
    futurists = {
      sculptor: "Umberto Boccioni",
      painter: "Vladimir Burliuk",
      poet: {
        name: "F.T. Marinetti",
        address: ["Via Roma 42R", "Bellagio, Italy 22021"]
      }
    };
    _ref = futurists.poet, name = _ref.name, (_ref1 = _ref.address, street = _ref1[0], city = _ref1[1]);
    alert(name + " — " + street);
    

    这个语法跟Erlang的模式匹配有点类似,不同的是,Erlang会严格匹配等号两边,不赋值的要用_作为占位符,否则运行时将抛出异常,而coffee则不会,对于不存在的成员,值为undefined:

    {poet: {nameX, address: [streetX, city]}} = futurists
    # nameX = "undefined"
    # streetX = "F.T. Marinetti"
    

    当然,对一个不存在的成员继续解析还是会抛出异常的:

    {poetX:{a}} = futurists
    # TypeError: Cannot read property 'a' of undefined
    

    另外,和JS一样,coffee也可以连续赋值:

    a=b=100
    # a=100
    # b=100
    

    假如将上面两种语法组合在一起,会怎样呢?就像下面的代码,最终d=?

    a={b:1,c:2,x:3,y:4}
    d={b,c}=a
    

    简单分析下:
    赋值语句的结合顺序是从右到左,所以 d={b,c}=a 等价于 d=({b,c}=a)
    我们还知道,赋值表达式的值是其本身,那么 {b,c}=a 的值是什么呢, {b,c} 还是 a ?
    可以尝试下,coffee会将语句 {b,c}=a 转成下面的JS:

    b = a.b, c = a.c;
    

    这样看来,d最终的值会是2,这也太奇怪了吧?

    还是看一下coffee把 d={b,c}=a 到底翻译成什么吧:

    d = (b = a.b, c = a.c, a);
    

    结果在意料之内,d的值不是 2
    结果在意料之外,d的值不是 {b,c}

    我一直以为,像 {b,c}=a 这样的表达式,返回值将会是 {b,c} 而不是 a ,然后 d={b,c}=a 可以按过滤器的方式执行。但是,我错了, {b,c}=a 这个表达式的值是 a
    再写几行代码验证一次:

    f = (a) -> {b,c}=a
    

    coffee将上面的代码翻译成

    f = function(a) {
      var b, c;
      return b = a.b, c = a.c, a;
    };
    

    好吧,解析赋值表达式的值确实是等号右边的值。
    至于 {b,c}=a 为何会被译成 b = a.b, c = a.c; 估计是因为该表达式的值不产生副作用,所以coffee把该表达式的值抛弃了。

    那么,在JS里,普通赋值表达式的值,是不是也是等号右边的值呢?下面的JS语句,result最终应该等于expr1还是expr2呢?

    result = expr1 = expr2
    

    乍眼一看,可能会觉得 result==expr1 result==expr2 ,等于哪个都一样。

    是的,在大多情况下,这都是成立的。但是,为什么对于上面的 {b,c}=a 这类语句,就不一定成立了呢?

    这是因为,不成立的原因是:执行 dest = src 后,dest 不一定等于 src

    JS中有类似的情形吗?答案当然是肯定的,看下面的JS代码:

    var obj, result;
    obj = {};
    Object.defineProperties(obj, {
      x: {
        set: function() {},
        get: function() {
          return 10;
        }
      }
    });
    

    将obj.x的读和写分离,就能产生类似的效果。

    obj.x = 11;
    result = obj.x;
    console.log(result); // 10
    

    回到正题,继续测试:

    result = obj.x = 11;
    console.log(result); // 11
    

    好吧,JS的赋值表达式的值也是等于等号右边的值。
    当最右值的读取包含副作用时,会怎样呢?

    var obj, r1, r2, r3;
    obj = {_x:1};
    Object.defineProperties(obj, {
      x: {
        set: function() {},
        get: function() {
          return ++this._x;
        }
      }
    });
    r1 = r2 = r3 = obj.x
    console.log(r1,r2,r3);  // 2 2 2
    

    嗯...我知道,在get方法包含带副作用的行为是不对的,这段代码只是测试而已~
    结果表明,连续赋值时最右值只计算一次。其实,从AST的执行过程来分析,最右值只计算一次,是合理的。

    ECMA文档 是这么描述赋值语句的:

    > 11.13.1 Simple Assignment ( = )
    > The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
    > 1. Let lref be the result of evaluating LeftHandSideExpression.
    > 2. Let rref be the result of evaluating AssignmentExpression.
    > 3. Let rval be GetValue(rref).
    > 4. Throw a SyntaxError exception if the following conditions are all true:
    > * Type(lref) is Reference is true
    > * IsStrictReference(lref) is true
    > * Type(GetBase(lref)) is Environment Record
    > * GetReferencedName(lref) is either "eval" or "arguments"
    > 5. Call PutValue(lref, rval).
    > 6. Return rval.
    > NOTE When an assignment occurs within strict mode code, its  LeftHandSide must not evaluate to an unresolvable 
    > reference. If it does a  ReferenceError exception is thrown upon assignment. The  LeftHandSide also may not be a 
    > reference to a data property with the attribute value  {[[Writable]]:false}, to an accessor property with the attribute value 
    > {[[Set]]:undefined}, nor to a non-existent property of an object whose [[Extensible]] internal property has the value false. In 
    > these cases a TypeError exception is thrown.
    

    赋值表达式的返回值是等号右边的值,连续赋值表达式的值自然就是最右值了,而且只会计算一次。

    那么,其他语言呢?

     

    C#

    C#也支持连续赋值,再加上坑爹的DateTime.Now,这个问题似乎也会有!
    不过嘛,如果写 a=b=DateTime.Now 能得到 a!=b 的概率似乎极小,写个坑爹的get属性吧:

    class Hole
    {
        public Hole(string name,int value)
        {
            _name = name;
            _value = value;
        }
        private string _name;
        private int _value;
        public int Value
        {
            get { Console.WriteLine("get {0} {1}", _name, _value); return _value; }
            set { Console.WriteLine("set {0}={1}", _name, value); }
        }
    }
    static void Main(string[] args)
    {
        var a = new Hole("a", 10);
        var b = new Hole("b", 11);
        var c = new Hole("c", 12);
        var d = c.Value = b.Value = a.Value;
        Console.WriteLine("d={0}",d);
        //get a 10
        //set b=10
        //set c=10
        //d=10
    }
    

    跟JS类似,C#的连续赋值里,赋值表达式的值也是等号右边的值,最右值也只会计算一次。dynamic呢?也一样。

    C# 4.0语言规范 7.17.1 这么描述赋值表达式:

    > The result of a simple assignment expression is the value assigned to the left operand. The result has the same type as the left operand and is always classified as a value.
    

     

    Erlang

    因为erlang的模式匹配属于严格匹配,左右值必然严格相同,并且变量不可变,似乎不会有上面的歧义。

    {_,Y}={X,_}={a,b}.   % {a,b}
    

    对于涉及到副作用的代码,像时间戳/随机值,就难说了:

    A=B=random:uniform(1234567890).  % okey
    

    没出现模式匹配错误,说明erlang中最右值也只计算一次。

     

    赋值表达式的值为何总是右值而不是左值?

    我估计,按赋值表达式的语意,等号的左边总是“可写”的,右边总是“可读”的。如果赋值表达式的值是左边的值,那么程序需要重新计算等号左边的值才能返回,这带来两个问题,一是性能,二是左边的对象不一定可读(例如那些可写但不可读的属性)。于是,几乎所有语言都将“所赋的值”作为赋值语句的返回值,所以连续赋值语句的行为总是“计算最右值一次,从右向左赋值多次”。

    但是,对于coffee的解构赋值表达式,情况则有点不同,因为coffee必须将该表达式拆分成两个,当c是一个包含副作用的属性时,a=b=c 和 b=c;a=c; 并不是等价的。所以,对于解构赋值表达式,等号右边的表达式实际将会求值多次。

  • 相关阅读:
    Python:FriendFeed的Tornado Web Server
    用psake来简化自动化脚本的构建
    Eucalyptus企业云计算
    兰州大学百年校庆风雨百年萃英路
    JQuery EasyUI弹出对话框解决Asp.net服务器控件无法执行后台代码的方法
    Winform开发框架之权限管理系统
    厚积薄发,丰富的公用类库积累,助你高效进行系统开发(2)常用操作
    Socket开发框架之数据采集客户端
    Winform开发框架之终极应用
    Winform分页控件使用详细介绍
  • 原文地址:https://www.cnblogs.com/neutra/p/2602361.html
Copyright © 2020-2023  润新知