前一段时间,我在这里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应归于他/她。
若大家发现有更好的展开形式,欢迎讨论。
分享到:
评论
关于if/else是否有and/or组装的等价式,答案是肯定的。
记得ibm developerworks有篇不错的文章,忘了是不是这个:
可爱的 Python: Python 中的函数编程
原文排版有点问题,这个盗链居然正常。
哈哈,原来rubynroll已经把lllyq的回贴合并到了主贴。
等价式还可以进一步简化为:
defined?是个关键字,其神奇效果类似javascript的typeof:
http://redhanded.hobix.com/inspect/methodCheckDefined.html
记得贴过一次这个链接,呵呵~
请看清楚标题,以及仔细阅读所有帖子再发表意见吧,否则就会和我犯下同样的错误。
这里讨论的是a ||= b的generic展开式,这个展开式不应该因a,b为特殊情况而有所区别,即语义的严格定义。
我想此贴的结论已经很明确了,如果对归纳的最终展开式还有什么异议,欢迎发表高见。
另外,对别人的知识妄下论断不会对让你显得更权威有什么帮助;同样,嵌入签名图片里的“资深会员”字样也不会。
真不知道你说了写什么,把Hash搞明白再来吧。
before catching what they are talking about, why did you demand others " 把Hash搞明白再来吧"?
plz read CAREFULLY before speak like this.
=> {}
>> h[:x]
=> 1
>> h[:x] ||= 2
=> 1
>> h
=> {}
陷进 ?
h[:x] 是未定义的,
a ||= b 相当于 a = b || a
这里因为你给Hash加了默认值,所以当然会输出1了。 看看下面的代码:
>> h = Hash.new
=> {}
>> h[:x] ||= 2
=> 2
>> h[:x] = h[:x] || 2
=> 1
>> h
=> {:x=>1}
>>
你上面这相当于给hash赋值了key-value对。
真不知道你说了写什么,把Hash搞明白再来吧。
想不到吧?
楼主还是学学基本知识吧, 这是想当然的东西, 还想不到?
http://github.com/rubyspec/rubyspec/tree/master/1.8/language/or_spec.rb
参考一些开源的rails代码,经常可以看到
@page = param[:page]
@page ||= 1
这样的代码,就是处理分页参数的。如果没有传page,就把@page设为1.
本身意思就很明瞭。
不过也有收获,代码还是写得简单清晰一点好。楼主分析辛苦了。
百度谷歌上都可以找到专门讲述ruby陷阱的资料。
ruby自己的文档也有专门讲述trap的部分。不是我杜撰啊。
从来没有看到单独使用ruby的项目,可能是我孤陋寡闻,除去开源库,我真的没发现。可能ruby的优势不在此,ror无疑是ruby最擅长的地方。
但随着groovy,grails发展和兴起,ruby或者ror的优势已经淡化。
毕竟c族群语言在当代具有先天性的亲和力。
ruby是否有前途?不知道,但是复杂而不严谨的语言驾驭起来有难度,使用起来有顾忌,团队整合上有困难。语言不是越复杂越好,而是越简单越好。
曲高和寡,先人早有断言。ruby是文言,而大众只需要大白话。用ruby无疑可以写出锦绣文章,但大部分人却看不懂也改不动。与其做老学究,不如现实一些。
对于象ruby这种特性丰富的语言,没有象JavaScript那样有一个定义严谨的规范来作参考,只能靠“咬文嚼字”了。
说“离开ror,ruby啥都不是“这个严重不同意. 单独来看ruby本身就是一门非常优秀的脚本语言,就“陷阱”而言并不比C++多,语言的表达能力并不在Perl之下而可读性非常之高,唯一缺憾是速度较慢,但对于脚本语言来说这个问题并不显著。
我开始用ruby的时候,根本就不知道什么是rails.
ruby按我说就是不断句的之乎者也,陷阱多多,没多少前途。最终还是死在这个陷阱上。
ruby的兴起完全是因为ror,离开ror,ruby啥都不是。
还是看好groovy,同为动态语言,却严谨的多。不必咬文嚼字玩文字游戏。
希望楼上能把“陷阱多多”一一列举而出,好有益于用ruby干活的劳苦大众。
ruby按我说就是不断句的之乎者也,陷阱多多,没多少前途。最终还是死在这个陷阱上。
ruby的兴起完全是因为ror,离开ror,ruby啥都不是。
还是看好groovy,同为动态语言,却严谨的多。不必咬文嚼字玩文字游戏。
但是David后来认为"a || a = b"应该更恰当些.
楼主是咬字头咬过头了,在一个极端的情况下(即a没有定义)写了一段代码然后造成了一些混乱,你又怎知david没有个潜台词是a has defined呢?
不过举的那个hash有默认值的||=这个问题是应该提起注意。
a = defined?(a) ? (a || a = b) : b
if a then a else a = 2 end
这个效果跟新开irb,执行
a || a = 2 效果是一样的,ruby 版本是1.8.6
效果不一样, 至少在版本是1.8.6. 1.8.7上是不一样的, 后者会raise undefined error.
根据你的回复我估计你还是没去试,显然,无论在1.8.6, 1.8.7,后者都会raise undefined error. 前者你试过了么?试过后你就知道为什么我会回第一个帖了\
我说的更清楚一点就是你测试下来两者行为不一样,那是因为你的测试方法不对,你没有每次都新开irb来测试,所以测试方法不对你看到的结果就不对,得出的结论也就不对了
哈哈我真是搞糊涂了,我试的是a ||= 2和a || a = 2, 比较的太多了,连自己都弄糊涂了, 看来此贴应该归新手贴才对:(
至于结论, 你认为 a ||= b最终展开为 if defined?(a) then (a || a = b) else a = b end 还有问题么?
if a then a else a = 2 end
这个效果跟新开irb,执行
a || a = 2 效果是一样的,ruby 版本是1.8.6
效果不一样, 至少在版本是1.8.6. 1.8.7上是不一样的, 后者会raise undefined error.
根据你的回复我估计你还是没去试,显然,无论在1.8.6, 1.8.7,后者都会raise undefined error. 前者你试过了么?试过后你就知道为什么我会回第一个帖了\
我说的更清楚一点就是你测试下来两者行为不一样,那是因为你的测试方法不对,你没有每次都新开irb来测试,所以测试方法不对你看到的结果就不对,得出的结论也就不对了
if a then a else a = 2 end
这个效果跟新开irb,执行
a || a = 2 效果是一样的,ruby 版本是1.8.6
效果不一样, 至少在版本是1.8.6. 1.8.7上是不一样的, 后者会raise undefined error.
非要用一个准确的展开式下面这个应该可以
if defined?(a) then (a || a = b) else a = nil end
这就是我所想要的讨论的,一个准确的展开式.
但是这个式子还是有问题, 应该是这样才对:
if defined?(a) then (a || a = b) else a = b end
所以结论是不是这样:
1) a ||= b 不等效于: a = a || b
2) 在a已经定义过时, a ||= b等效于a || a = b
3) 在a未定义时, a ||= b等效于a = b
所以归纳起来,准确的展开式应该是:
if defined?(a) then (a || a = b) else a = b end
意见??
h[:x] ||= 2 并没有对h[:x]赋值,相当于h[:x] || h[:x] = 2,而h[:x] = nil是有一个赋值动作
我举的那个例子就是为了反驳
同意楼上,感觉应该相当于
a = nil
a || a = b
caryl说的"楼上"就是
另外, h[:x] ||= 2 相当于h[:x] || h[:x] = 2在这里之所以成立,是因为h这个Hash是已经定义过了. 对于一个通用的 a ||= b, 并不能简单地展开成 a || a = b, 因为这里的a如果未定义就会是个例外.
注意, 这里探讨的是"完全等同"的展开式, 因为如果存在特例, 除非语言规范明白地告诉你"这是个特例", 否则'陷阱'就有可能在那里等着你.
先执行一边楼主的代码
可见针对楼主hash的代码,david观点没错。
楼主应该知道,hash的[]是个方法,用个简单的类模拟一下
试一下:
当然,我前面说的相当于
是不能满足上边A的例子的(同样对楼主hash的例子有问题),但是如果根据上边的理解,david说的 a ||= b 相当于 a || a = b没什么问题。
所以我说支持 lllyq 的观点,可能是 ||= 因为随后就是赋值操作,没有raise undefined错误?这可能真得看了源码才知道了。
if a then a else a = 2 end
这个效果跟新开irb,执行
a || a = 2 效果是一样的,ruby 版本是1.8.6
非要用一个准确的展开式下面这个应该可以
if defined?(a) then (a || a = b) else a = nil end
rubynroll的意思是
你新开irb 执行a ||= 2和
新开irb执行a || a = 2的结果是不一样的,所以这2个公式不是等价的
这样说来我前面的公式也不行...
这个我没有否定,我一开始只是说不用搞这么复杂去涉及define的问题,而且他的结论展开式为 if a then a else a = 2 end 是不对的,看我第一个回帖的分析,以及上一个回帖的说明
if a then a else a = 2 end
这个效果跟新开irb,执行
a || a = 2 效果是一样的,ruby 版本是1.8.6
非要用一个准确的展开式下面这个应该可以
if defined?(a) then (a || a = b) else a = nil end
rubynroll的意思是
你新开irb 执行a ||= 2和
新开irb执行a || a = 2的结果是不一样的,所以这2个公式不是等价的
这样说来我前面的公式也不行...