`
rubynroll
  • 浏览: 204646 次
  • 性别: Icon_minigender_1
  • 来自: Wgt
社区版块
存档分类
最新评论

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

阅读更多
前一段时间,我在这里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应归于他/她。

若大家发现有更好的展开形式,欢迎讨论。
分享到:
评论
11 楼 lllyq 2008-06-28  
你可以试试看新开irb,执行
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
10 楼 rubynroll 2008-06-28  
lllyq 写道
讨论研究是相互的,你是不是太自信了,事实上你的每一行代码我从一开始都试过了才回贴的,希望看到这里的朋友也试试看,LZ不愿意尝试也没问题,你们自己试试看是怎样,否则可能会被这个标题误导

另外关于define的问题还有另一个解释,一般我们都在方法体用,就不存在define的问题,因为如果未定义会自动使用local变量


老实说不是我不愿意尝试,而是我不知道如何根据你的说法来尝试.

其实对于这个问题我自己也是一肚子疑惑, 所以才会发文, 看看大家是否还有更好的解释? Quake Wang就纠正了我的结论.

但从你的回复中,我实在不知道如何去根据你的"说法"转换成展开式, 恕我愚钝, 请指点, 希望继续讨论

9 楼 lllyq 2008-06-28  
讨论研究是相互的,你是不是太自信了,事实上你的每一行代码我从一开始都试过了才回贴的,希望看到这里的朋友也试试看,LZ不愿意尝试也没问题,你们自己试试看是怎样,否则可能会被这个标题误导

另外关于define的问题还有另一个解释,一般我们都在方法体用,就不存在define的问题,因为如果未定义会自动使用local变量
8 楼 rubynroll 2008-06-28  
拜托, 我要的是"展开式", 不是"说法".

还是那句话: 用点例子好不好?
7 楼 lllyq 2008-06-28  
lllyq 写道

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

那你就再看看我这个说法吧,不妨一试
6 楼 rubynroll 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如果未定义就会是个例外.

注意, 这里探讨的是"完全等同"的展开式, 因为如果存在特例, 除非语言规范明白地告诉你"这是个特例", 否则'陷阱'就有可能在那里等着你.
5 楼 lllyq 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是有一个赋值动作
4 楼 rubynroll 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的版本似乎可以避免这个问题.





3 楼 caryl 2008-06-28  
同意楼上,感觉应该相当于
a = nil
a || a = b
2 楼 lllyq 2008-06-27  
没这么复杂吧,上面的hash的问题不过就是hash default机制的问题而已,hash[:x]只是一个方法调用而已

a || a = b 就是 a || a = b,本来就没考虑defined?的问题,跟 if a then a else a = b end 一样的,如果你执行下来不一样那估计是你连续执行的问题,前一句相当于做了define
1 楼 QuakeWang 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源代码才能明白到底是怎么回事。

相关推荐

    ruby-progressbar:RubyProgressBar是Ruby的文本进度栏库

    Ruby /进度栏 Ruby的终极文本进度栏库! 纯粹带动进度条刺激,将您吓到头顶! 不要错过所有孩子都在谈论的话题! 如果您想让所有人都知道您的宝石或应用程序可以在笼子中生存,那么您想要Ruby级! 比其他186,312个...

    pusher-http-ruby, Pusher | owner=@vivangkumar的ruby 服务器库.zip

    pusher-http-ruby, Pusher | owner=@vivangkumar的ruby 服务器库 推 gem 安装&配置将推送器添加到你的Gemfile,然后运行 bundle installgem 'pusher'或者通过 gem 安装gem install pusher在 http://p

    Ruby-Braintree提供集成访问Braintree网关

    Ruby-Braintree是Ruby编程语言中的一个库,用于与Braintree支付网关进行无缝集成。Braintree是一家全球领先的支付处理服务提供商,为在线商家提供了处理信用卡和其他电子支付方式的平台。通过Ruby-Braintree库,...

    ruby-client:用于Ruby的Netlify API客户端

    Netlify Ruby客户端 ====================== Netlify是用于可编程Web的托管服务。 它了解您的文档,处理表单,并允许您通过其API进行部署,管理表单提交,将javascript代码片段注入网站并进行HTML文档的智能更新。 ...

    Ruby语言入门教程:从安装到编写

    这只是Ruby编程之旅的起点。Ruby的世界里还有更多值得探索的内容,比如元编程、模块、Gem管理、Rails框架等。随着实践的深入,你会逐渐发现Ruby语言的魅力所在。继续学习,参与社区,编写出既优雅又高效的代码,享受...

    Ruby-QRcode一个用于解析QR码的Ruby库

    此外,Ruby-QRcode库也支持SVG输出,这对于在Web应用中生成响应式QR码非常有用,因为SVG可以无损缩放。使用`to_svg`方法即可生成SVG格式的QR码: ```ruby svg_data = qr.as_svg(scale: 4) ``` 总的来说,Ruby-...

    Ruby与JSON:无缝数据交换的秘诀

    ### Ruby与JSON:无缝数据交换的秘诀 #### Ruby概述 Ruby是一种高级的、面向对象的编程语言,由日本开发者松本行弘(Yukihiro "Matz" Matsumoto)在1995年创建。该语言的设计目标是提供简单且自然的编程体验,同时...

    selenium-webdriver 2.42.0 for Ruby

    selenium-webdriver 2.42.0 for Ruby Runtime Dependencies:childprocess >= 0.5.0

    Ruby语言教程:从入门到实践.md

    Ruby语言教程:从入门到实践.md

    jlong4223:我的个人资料库

    Ruby| 数据库: | MongoDB | PostgreSQL | MySQL | ClearDB | 前端: | HTML5 | CSS3 | SASS | 引导带| NPM || React.js | Vue.js | AngularJS | jQuery | 后端: | Node.js | Ruby on Rails |中文(简体) Express...

    Ruby入门教程:只需20分钟,你将了解到ruby语言的基础知识

    #### 二、交互式Ruby (IRB) - **启动IRB**: - **Mac OS X**: 打开终端窗口,输入`irb`。 - **Linux**: 打开Shell,输入`irb`。 - **Windows**: 在开始菜单中找到Ruby -> FXRI 并执行。 - **基本操作**: - 输入...

    fosl:使用lsof(1)的Ruby API

    fosl,Ruby lsof API 地位: 该API现在可以使用,但是它不是真正的API,而是对lsof输出的轻度散列包装。示例:“ lsof.rb” 代码: : 这个示例像通常一样运行'lsof',但是将输出捕获到ruby中。 % sudo ruby lsof.rb...

    Ruby 连接数据库资源汇总

    Ruby 是一种动态、开源的编程语言,以其简洁和表达力强的特点受到开发者的喜爱。在IT行业中,数据库连接是常见的任务,无论是在Web应用开发还是数据处理中。Ruby 提供了多种方式来连接不同的数据库系统,如 MySQL、...

    hash_formatter:Hash Formatter 是一个为代码编辑器格式化 Ruby 哈希的库

    这是一个用于格式化 Ruby 哈希的小型简单库,旨在供文本编辑器使用。 它使用和而不是正则表达式,让它可以轻松解析最棘手的 Ruby 代码。 目前,它的格式选项是固执和简单化的。 您可以使用以下格式格式化 Ruby 哈希...

    zinbei_zero:津北の初期です

    ))~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(( (( )) )) ========= ====== ||\ || ||======== ||========= ====== (( (( / / || || \ || || )) || || )) )) / / || || \ || || )) || |...

    ruby安装包下载 | ruby环境搭建

    1. 安装Ruby: `ruby-2.7.2.tar.gz` 是Ruby的源代码包,版本为2.7.2。首先,你需要解压这个文件: ``` tar -zxvf ruby-2.7.2.tar.gz ``` 进入解压后的目录,执行编译和安装: ``` cd ruby-2.7.2 ./configure...

    ruby-course:Ruby简介

    在给定的"ruby-course"中,你将学习如何使用`If / Else`来创建一个简单的交互式程序。这个程序会询问用户最喜欢的颜色,并根据用户输入给出相应的回应。下面是一个示例代码: ```ruby puts "请输入你最喜欢的颜色:...

    Beginning.Ruby.From.Novice.to.Professional.3rd.Edition.1484212797

    Learn the principles behind object-oriented programming and within a few chapters create a fully functional Ruby application. You'll also gain a basic understanding of many ancillary technologies such...

    ruby_version:Ruby版 | 比RUBY_VERSION好

    Ruby 版本 提供RubyVersion以简化在程序中检查正确 Ruby 版本的过程。安装 在您的命令行上: $ gem install ruby_version 在Ruby中: require 'ruby_version'用法 # Output RUBY_VERSIONRubyVersion . to_s# Check ...

    ruby-calendar:使用 Ruby 构建的独立日历实用程序

    日历实用程序 PORO(普通的旧Ruby对象)。示例用法: > Calendar.for_week(4).of_month(8).in_year(2012).show{:wed=>22, :thu=>23, :fri=>24, :sat=>25, :sun=>26, :mon=>27, :tue=>28}> Calendar.for_month(6).in_...

Global site tag (gtag.js) - Google Analytics