论坛首页 编程语言技术论坛

Ruby'陷阱'之: '||=' 的真正展开式

浏览 9076 次
精华帖 (0) :: 良好帖 (9) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-06-27  
前一段时间,我在这里http://rubynroll.iteye.com/blog/192547展示了一个空格带来的'陷阱', 今天又见到另一个'陷阱'(http://dablog.rubypal.com/2008/3/25/a-short-circuit-edge-case by David).

之所以为陷阱加引号, 是因为大部分情况下我们都没有机会掉进去

大多数Ruby教科书在解释 "a ||= b" 这个复合操作时,都说她等效于: "a = a || b", 实际上真的如此么?

让我们在irb里面来看看:

h = Hash.new(1)  # 生成一个新的Hash,缺省值为1
=> {}
h[:x]
=> 1    # h[:x]未定义,所以返回1
h[:x] ||= 2
=> 1
h
=> {}   # 啊? '陷阱'?
h[:x] = h[:x] || 2
=> 1
h
=> {:x=>1}  # 啊!


David提到,Matz在RubyConf 2007上的解释是"a ||= b"真实的等价应该是: "a or a = b", 但是David后来认为"a || a = b"应该更恰当些.

果真的如此么? 不!

一个简单的例子可以反驳, 如果a未定义,则:

irb(main):001:0> a || a = 2
NameError: undefined local variable or method `a' for main:Object
        from (irb):1
        from :0


所以, 看来"a ||= b"正确的展开应该是: "a = b unless a"

对了么? 还是不对!

"a ||= b"表达式最终返回的是a, 而"a = b unless a"在a非真时返回nil.

所以, "a ||= b"的正确展开式应该为: "if a then a else a = b end"


想不到吧?

=== 补充 ===

鉴于此帖较长,且中间夹杂着不少因我的疏忽和错误造成的无谓争论,因此把最终的到的结论列在这里,免得看起来太累,或者有些人没有耐心看完而增加更多的无谓的争论。

结论如下:
1) a ||= b 不等效于: a = a || b
2) 归纳起来,准确的展开式应该是:
if defined?(a) then (a || a = b) else a = b end

特别感谢lllyq, 这个结论的credit应归于他/她。

若大家发现有更好的展开形式,欢迎讨论。
   发表时间:2008-06-27  
按照你最终结论的展开,那么下面的代码中h[:x]将会被执行2次,在屏幕上打印出2个x,但是实际上它只被执行了一次:
h = Hash.new {|hash, key| puts key; 1}
h[:x] ||= 3


难道是:
if temp = a then temp else a = b end

可能要去看一下ruby源代码才能明白到底是怎么回事。
0 请登录后投票
   发表时间:2008-06-27  
没这么复杂吧,上面的hash的问题不过就是hash default机制的问题而已,hash[:x]只是一个方法调用而已

a || a = b 就是 a || a = b,本来就没考虑defined?的问题,跟 if a then a else a = b end 一样的,如果你执行下来不一样那估计是你连续执行的问题,前一句相当于做了define
0 请登录后投票
   发表时间:2008-06-28  
同意楼上,感觉应该相当于
a = nil
a || a = b
0 请登录后投票
   发表时间:2008-06-28  
lllyq写的我没看明白....拜托用点例子好不好?


caryl 写道
同意楼上,感觉应该相当于
a = nil
a || a = b


你在irb里面试一下就知道了,两者并不等价:

irb(main):014:0> h = Hash.new(1)
=> {}
irb(main):015:0> h[:x] ||= 2
=> 1
irb(main):016:0> h
=> {}
irb(main):017:0> h[:x] = nil
=> nil
irb(main):018:0> h[:x] || h[:x] = 2
=> 2
irb(main):019:0> h
=> {:x=>2}


所以不要以为看似简单的东西就可以想当然!

我的结论当然还是有问题,a被引用了两次. Quaka Wang的版本似乎可以避免这个问题.





0 请登录后投票
   发表时间:2008-06-28  
rubynroll 写道
lllyq写的我没看明白....拜托用点例子好不好?


caryl 写道
同意楼上,感觉应该相当于
a = nil
a || a = b


你在irb里面试一下就知道了,两者并不等价:

irb(main):014:0> h = Hash.new(1)
=> {}
irb(main):015:0> h[:x] ||= 2
=> 1
irb(main):016:0> h
=> {}
irb(main):017:0> h[:x] = nil
=> nil
irb(main):018:0> h[:x] || h[:x] = 2
=> 2
irb(main):019:0> h
=> {:x=>2}


所以不要以为看似简单的东西就可以想当然!

我的结论当然还是有问题,a被引用了两次. Quaka Wang的版本似乎可以避免这个问题.



这个h[:x]行为有什么特别么?跟预期的行为一致啊
h[:x] ||= 2 并没有对h[:x]赋值,相当于h[:x] || h[:x] = 2,而h[:x] = nil是有一个赋值动作
0 请登录后投票
   发表时间:2008-06-28  
lllyq 写道
这个h[:x]行为有什么特别么?跟预期的行为一致啊
h[:x] ||= 2 并没有对h[:x]赋值,相当于h[:x] || h[:x] = 2,而h[:x] = nil是有一个赋值动作


我举的那个例子就是为了反驳
caryl 写道

同意楼上,感觉应该相当于
a = nil
a || a = b


caryl说的"楼上"就是
lllyq 写道
a || a = b 就是 a || a = b,本来就没考虑defined?的问题,跟 if a then a else a = b end 一样的,如果你执行下来不一样那估计是你连续执行的问题,前一句相当于做了define



另外, h[:x] ||= 2 相当于h[:x] || h[:x] = 2在这里之所以成立,是因为h这个Hash是已经定义过了. 对于一个通用的 a ||= b, 并不能简单地展开成 a || a = b, 因为这里的a如果未定义就会是个例外.

注意, 这里探讨的是"完全等同"的展开式, 因为如果存在特例, 除非语言规范明白地告诉你"这是个特例", 否则'陷阱'就有可能在那里等着你.
0 请登录后投票
   发表时间:2008-06-28  
lllyq 写道

a || a = b 就是 a || a = b,本来就没考虑defined?的问题,跟 if a then a else a = b end 一样的,如果你执行下来不一样那估计是你连续执行的问题,前一句相当于做了define

那你就再看看我这个说法吧,不妨一试
0 请登录后投票
   发表时间:2008-06-28  
拜托, 我要的是"展开式", 不是"说法".

还是那句话: 用点例子好不好?
0 请登录后投票
   发表时间:2008-06-28  
讨论研究是相互的,你是不是太自信了,事实上你的每一行代码我从一开始都试过了才回贴的,希望看到这里的朋友也试试看,LZ不愿意尝试也没问题,你们自己试试看是怎样,否则可能会被这个标题误导

另外关于define的问题还有另一个解释,一般我们都在方法体用,就不存在define的问题,因为如果未定义会自动使用local变量
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics