2008-03-07
Try() 和 Maybe Monad
在Ruby里用到对象图导航(Object Graph Navigation)的时候,有时候需要判断对象是否为nil,很常见这样的代码:
ozmm.org的chris最近介绍了一个好方法,他给这个方法起名叫try(),给Object添加一个try方法:
这样上面的代码就可以简化成
这个try的实际用途很多,比如:
但是这个简单的try()有很多限制,比如原先这样的代码就不能解决:
对此Anders Engström提供了一个Improved 'try()',上面的代码可以简化成
但是又有人觉得这样不够直观了,提供了一个类似Groovy做法的: A better “try()” for Ruby, why not do the Groovy way?
Groovy语言本身提供了内置的?.操作: person?.name,而上面这篇文章则通过在方法名后面添加"_"来实现相同的目的
Q1: 和方法2相比,它少了一个默认值的处理,大家觉得添加这样的特性如何?
上面这3种try()方法都是通过method missing实现的,这篇文章提到的Maybe Monad也可以解决这个问题:
Q2: 这些方法你更喜欢用哪一种呢?或者你有其他更好的方法?欢迎讨论。
我比较喜欢方法2,感觉代码侵入比较小,缺点是多层导航的时候不够直观。而方法4Maybe Monad的优缺点正好与之相反,如果能综合这2种方法就好了。
#显示某个产品的分类名称 product.category ? product.category.name : nil
ozmm.org的chris最近介绍了一个好方法,他给这个方法起名叫try(),给Object添加一个try方法:
class Object
def try(method)
send method if respond_to? method
end
end
这样上面的代码就可以简化成
product.category.try(:name)
这个try的实际用途很多,比如:
#删除某个可能存在的用户
User.find_by_name("JavaEye").try(:destroy)
#找出最后一个未激活用户的名字
User.find_all_by_active(false).last.try(:name)
但是这个简单的try()有很多限制,比如原先这样的代码就不能解决:
#默认值 product.category ? product.category.name : "N/A" #多层对象图导航 product.category.owner.name
对此Anders Engström提供了一个Improved 'try()',上面的代码可以简化成
product.category.try(:name, :default => "N/A") product.category.try(:owner, :name)
但是又有人觉得这样不够直观了,提供了一个类似Groovy做法的: A better “try()” for Ruby, why not do the Groovy way?
Groovy语言本身提供了内置的?.操作: person?.name,而上面这篇文章则通过在方法名后面添加"_"来实现相同的目的
product.category.owner_.name_
Q1: 和方法2相比,它少了一个默认值的处理,大家觉得添加这样的特性如何?
product.category.name_(:default => "N/A")
上面这3种try()方法都是通过method missing实现的,这篇文章提到的Maybe Monad也可以解决这个问题:
Maybe.new(product).category.name.value("N/A")
Q2: 这些方法你更喜欢用哪一种呢?或者你有其他更好的方法?欢迎讨论。
我比较喜欢方法2,感觉代码侵入比较小,缺点是多层导航的时候不够直观。而方法4Maybe Monad的优缺点正好与之相反,如果能综合这2种方法就好了。
评论
Trustno1
2008-03-12
这个Maybe monad,好丑好丑
liusong1111
2008-03-11
嗯,在active_support的whiny_nil.rb里对nil.id进行改写的:
我上面又犯了错误,既然它是raise RuntimeError,那么 nil.id rescue nil就应该是正常返回nil而不应报任何warning/error信息。
我们在生产环境下却遇到了warning且返回4的情况,是因为rails并不是在初始化时直接加载whiny_nil.rb,而是
development.rb和test.rb里都有:
production.rb里没有配置,所以取:
造成开发、测试环境跑的好好的程序到生产环境出错,所以inline rescue还是不能用,即使开发环境是正确的。
class NilClass
def id
raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller
end
end
我上面又犯了错误,既然它是raise RuntimeError,那么 nil.id rescue nil就应该是正常返回nil而不应报任何warning/error信息。
我们在生产环境下却遇到了warning且返回4的情况,是因为rails并不是在初始化时直接加载whiny_nil.rb,而是
require('active_support/whiny_nil') if configuration.whiny_nils
development.rb和test.rb里都有:
config.whiny_nils = true
production.rb里没有配置,所以取:
def default_whiny_nils
false
end
造成开发、测试环境跑的好好的程序到生产环境出错,所以inline rescue还是不能用,即使开发环境是正确的。
rainchen
2008-03-10
liusong1111 写道
仔细看了下,rails在处理nil.id时不是输出warning,而是明明白白的RuntimeError,所以
因为用了rescue会log出error但依然返回4。
也不会像预期返回nil,而是报RuntimeError。
现象不同,可都不对啊。
User.find_by_name("sliu").id rescue nil
因为用了rescue会log出error但依然返回4。
User.find_by_name("sliu").try(:id)
也不会像预期返回nil,而是报RuntimeError。
现象不同,可都不对啊。
这是经典的Null Object Pattern问题嘛,貌似在一篇讨论patterns in ruby的BLOG里有见过讨论
try()里可加上先判断自身是否是nil,然后再respond_to?
liusong1111
2008-03-10
仔细看了下,rails在处理nil.id时不是输出warning,而是明明白白的RuntimeError,所以
因为用了rescue会log出error但依然返回4。
也不会像预期返回nil,而是报RuntimeError。
现象不同,可都不对啊。
User.find_by_name("sliu").id rescue nil
因为用了rescue会log出error但依然返回4。
User.find_by_name("sliu").try(:id)
也不会像预期返回nil,而是报RuntimeError。
现象不同,可都不对啊。
liusong1111
2008-03-10
try或rescue都防不住rails的一个陷阱:nil.id的问题。
假设有这样的代码:
当User.find_by_name是nil时,整个表达式并不会引发异常,而是返回怪异的数字4,并附带打印一个warning。
在纯ruby环境中打印的warning信息是:
在rails环境中打印出的warning信息是:
这是因为id方法是在Object中定义的,返回值大致可以理解为对象的内存地址,nil也是一个Object,所以。。。
貌似因为rails把id方法改写成 代表数据库记录的主键 后,ruby语言为了防止二义性就把Object#id deprecated掉了,取而代之的是Object#object_id。而rails多数情况下能接收id的时候也能接收一个ActiveRecord对象,尽可能避免错误的返回4而不是nil。总之因为现在是warning而不是error,所以如果不检查log的话,有可能系统正常运行,却在某些情况下出现诡异的逻辑问题。
假设有这样的代码:
User.find_by_name("sliu").id
当User.find_by_name是nil时,整个表达式并不会引发异常,而是返回怪异的数字4,并附带打印一个warning。
在纯ruby环境中打印的warning信息是:
引用
Object#id will be deprecated; use Object#object_id
在rails环境中打印出的warning信息是:
引用
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil,
use object_id
use object_id
这是因为id方法是在Object中定义的,返回值大致可以理解为对象的内存地址,nil也是一个Object,所以。。。
貌似因为rails把id方法改写成 代表数据库记录的主键 后,ruby语言为了防止二义性就把Object#id deprecated掉了,取而代之的是Object#object_id。而rails多数情况下能接收id的时候也能接收一个ActiveRecord对象,尽可能避免错误的返回4而不是nil。总之因为现在是warning而不是error,所以如果不检查log的话,有可能系统正常运行,却在某些情况下出现诡异的逻辑问题。
liusong1111
2008-03-07
puts a.b.c.d rescue "N/A"
等价于
(puts a.b.c.d) rescue "N/A"
而
puts (a.b.c.d rescue "N/A")
会出错:
引用
syntax error, unexpected kRESCUE_MOD, expecting ')'
puts (a.b.c.d rescue "N/A")
^
(irb):21: syntax error, unexpected ')', expecting $end
puts (a.b.c.d rescue "N/A")
^
(irb):21: syntax error, unexpected ')', expecting $end
貌似是ruby解析器或语法定义的bug,它不认为a.b.c rescue "N/A"整体作为puts的唯一一个参数。
这种用法民间好多称之为 inline rescue。
groovy的机制是叫safe navigation吧,用来特意处理变量为null时的情况,很早以前记得它是这样用的:
a->b->c
现在成了
a?.b?.c
我还是喜欢老的表示方式。
另种形式:
a && a.b && a.b.c
lllyq
2008-03-07
a.b.c.d rescue "N/A" 好像只能用到块里,例如 puts a.b.c.d rescue "N/A"就不行了
我修正了一下代码,应该没什么副作用了
a.b.c.d.default()
我修正了一下代码,应该没什么副作用了
class Object
def default(r)
self
end
end
class NilClass
@@caller_cache = {}
@@file_cache = {}
alias old_method_missing method_missing
def default(r=nil)
r
end
def method_missing(sym)
@@caller_cache[caller.first] ||= begin
x, y = caller.first.split(":")
@@file_cache[x] ||= File.read(x).split("\n")
@@file_cache[x][y.to_i-1].index(".default").nil?
end
@@caller_cache[caller.first] ? old_method_missing(sym) : nil
end
end
a.b.c.d.default()
liusong1111
2008-03-07
x = a.b.c.d rescue "N/A"
lllyq
2008-03-07
lllyq 写道
可以这样
category.name.default()
category.name.default("N/A")
class NilClass def default(r=nil) r end def method_missing(sym) nil #这个不写也可以 end end
category.name.default()
category.name.default("N/A")
不过这样也不好,仅最后一级nil没问题,但如果是多级nil,则屏蔽了该有的信息,应该在method_missing里面trace,如果最终调用的是default才做这个操作,等会再改改
lllyq
2008-03-07
可以这样
category.name.default()
category.name.default("N/A")
class NilClass def default(r=nil) r end def method_missing(sym) nil #这个不写也可以 end end
category.name.default()
category.name.default("N/A")
simohayha
2008-03-07
我还看到过一个方法,就是给NilClass定义一个method_missing方法..
发表评论
提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则
- 浏览: 27235 次
- 性别:

- 来自: 上海

- 详细资料
搜索本博客
我的相册
M_100_4350
共 30 张
共 30 张
最近加入圈子
最新评论
-
Java程序员应该学习Ruby
[quote="baichinie"]public interface List ...
-- by unique.wu -
Java程序员应该学习Ruby
看了这个帖子,我终于找到了在本论坛可以多得分的秘密途径,也就是捷径,那就是多写R ...
-- by ltian -
Java程序员应该学习Ruby
以前我主要用.net, 现在改用python, 无论如何不想换回去了. 不过, ...
-- by 白发红颜 -
Java程序员应该学习Ruby
geszJava 写道ruby没啥吸引力,还是groovy好,不容易犯错。rub ...
-- by liusong1111 -
Java程序员应该学习Ruby
ruby没啥吸引力,还是groovy好,不容易犯错。ruby陷阱太多了,如果不是 ...
-- by geszJava






评论排行榜