锁定老帖子 主题:Try() 和 Maybe Monad
精华帖 (14) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-03-07
#显示某个产品的分类名称 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种方法就好了。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-03-07
我还看到过一个方法,就是给NilClass定义一个method_missing方法..
|
|
返回顶楼 | |
发表时间:2008-03-07
可以这样
class NilClass def default(r=nil) r end def method_missing(sym) nil #这个不写也可以 end end category.name.default() category.name.default("N/A") |
|
返回顶楼 | |
发表时间:2008-03-07
lllyq 写道 可以这样
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才做这个操作,等会再改改 |
|
返回顶楼 | |
发表时间:2008-03-07
x = a.b.c.d rescue "N/A" |
|
返回顶楼 | |
发表时间:2008-03-07
a.b.c.d rescue "N/A" 好像只能用到块里,例如 puts a.b.c.d rescue "N/A"就不行了
我修正了一下代码,应该没什么副作用了 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() |
|
返回顶楼 | |
发表时间: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 貌似是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 |
|
返回顶楼 | |
发表时间:2008-03-10
try或rescue都防不住rails的一个陷阱:nil.id的问题。
假设有这样的代码: 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 这是因为id方法是在Object中定义的,返回值大致可以理解为对象的内存地址,nil也是一个Object,所以。。。 貌似因为rails把id方法改写成 代表数据库记录的主键 后,ruby语言为了防止二义性就把Object#id deprecated掉了,取而代之的是Object#object_id。而rails多数情况下能接收id的时候也能接收一个ActiveRecord对象,尽可能避免错误的返回4而不是nil。总之因为现在是warning而不是error,所以如果不检查log的话,有可能系统正常运行,却在某些情况下出现诡异的逻辑问题。 |
|
返回顶楼 | |
发表时间:2008-03-10
仔细看了下,rails在处理nil.id时不是输出warning,而是明明白白的RuntimeError,所以
User.find_by_name("sliu").id rescue nil 因为用了rescue会log出error但依然返回4。 User.find_by_name("sliu").try(:id) 也不会像预期返回nil,而是报RuntimeError。 现象不同,可都不对啊。 |
|
返回顶楼 | |
发表时间:2008-03-10
liusong1111 写道 仔细看了下,rails在处理nil.id时不是输出warning,而是明明白白的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? |
|
返回顶楼 | |