精华帖 (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应归于他/她。 若大家发现有更好的展开形式,欢迎讨论。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间: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源代码才能明白到底是怎么回事。 |
|
返回顶楼 | |
发表时间:2008-06-27
没这么复杂吧,上面的hash的问题不过就是hash default机制的问题而已,hash[:x]只是一个方法调用而已
a || a = b 就是 a || a = b,本来就没考虑defined?的问题,跟 if a then a else a = b end 一样的,如果你执行下来不一样那估计是你连续执行的问题,前一句相当于做了define |
|
返回顶楼 | |
发表时间:2008-06-28
同意楼上,感觉应该相当于
a = nil a || a = b |
|
返回顶楼 | |
发表时间: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的版本似乎可以避免这个问题. |
|
返回顶楼 | |
发表时间: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是有一个赋值动作 |
|
返回顶楼 | |
发表时间: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如果未定义就会是个例外. 注意, 这里探讨的是"完全等同"的展开式, 因为如果存在特例, 除非语言规范明白地告诉你"这是个特例", 否则'陷阱'就有可能在那里等着你. |
|
返回顶楼 | |
发表时间:2008-06-28
lllyq 写道 a || a = b 就是 a || a = b,本来就没考虑defined?的问题,跟 if a then a else a = b end 一样的,如果你执行下来不一样那估计是你连续执行的问题,前一句相当于做了define 那你就再看看我这个说法吧,不妨一试 |
|
返回顶楼 | |
发表时间:2008-06-28
拜托, 我要的是"展开式", 不是"说法".
还是那句话: 用点例子好不好? |
|
返回顶楼 | |
发表时间:2008-06-28
讨论研究是相互的,你是不是太自信了,事实上你的每一行代码我从一开始都试过了才回贴的,希望看到这里的朋友也试试看,LZ不愿意尝试也没问题,你们自己试试看是怎样,否则可能会被这个标题误导
另外关于define的问题还有另一个解释,一般我们都在方法体用,就不存在define的问题,因为如果未定义会自动使用local变量 |
|
返回顶楼 | |