Ruby像nil的对象
如何在ruby中创建一个对象,该对象在类似于nil的逻辑表达式中将被评估为false?
How can I create an Object in ruby that will be evaluated to false in logical expressions similar to nil?
我的目的是在其他对象上启用嵌套调用,这些对象通常位于链的一半位置,其值通常为nil
,但允许所有调用继续进行-返回我的类似nil的对象,而不是nil
本身.该对象将返回它自己,以响应它不知道如何处理的任何收到的消息,并且我预计我将需要实现一些替代方法,例如nil?
.
My intention is to enable nested calls on other Objects where somewhere half way down the chain a value would normally be nil
, but allow all the calls to continue - returning my nil-like object instead of nil
itself. The object will return itself in response to any received messages that it does not know how to handle and I anticipate that I will need to implement some override methods such as nil?
.
例如:
fizz.buzz.foo.bar
如果fizz
的buzz
属性不可用,我将返回我的类似nil的对象,该对象将一直接受调用,直到bar
自行返回.最终,上面的语句应评估为false.
If the buzz
property of fizz
was not available I would return my nil-like object, which would accept calls all the way down to bar
returning itself. Ultimately, the statement above should evaluate to false.
基于以下所有出色的答案,我提出了以下建议:
Based on all the great answers below I have come up with the following:
class NilClass
attr_accessor :forgiving
def method_missing(name, *args, &block)
return self if @forgiving
super
end
def forgive
@forgiving = true
yield if block_given?
@forgiving = false
end
end
这允许使用一些令人毛骨悚然的技巧,例如:
This allows for some dastardly tricks like so:
nil.forgiving {
hash = {}
value = hash[:key].i.dont.care.that.you.dont.exist
if value.nil?
# great, we found out without checking all its parents too
else
# got the value without checking its parents, yaldi
end
}
很显然,您可以将此块透明地包装在某些函数调用/类/模块/任何地方.
Obviously you could wrap this block up transparently inside of some function call/class/module/wherever.
这是一个相当长的答案,其中包含有关如何解决该问题的大量想法和代码示例.
This is a pretty long answer with a bunch of ideas and code samples of how to approach the problem.
Rails具有 try方法,让您像这样编程.这是它的实现方式:
Rails has a try method that let's you program like that. This is kind of how it's implemented:
class Object
def try(*args, &b)
__send__(*a, &b)
end
end
class NilClass # NilClass is the class of the nil singleton object
def try(*args)
nil
end
end
您可以像这样编程:
fizz.try(:buzz).try(:foo).try(:bar)
可以想象,您可以对此进行修改,使其工作方式有所不同,以支持更优雅的API:
You could conceivably modify this to work a little differently to support a more elegant API:
class Object
def try(*args)
if args.length > 0
method = args.shift # get the first method
__send__(method).try(*args) # Call `try` recursively on the result method
else
self # No more methods in chain return result
end
end
end
# And keep NilClass same as above
那么你可以做:
fizz.try(:buzz, :foo, :bar)
andand
and 使用一种更邪恶的技术来破解事实您不能直接实例化NilClass子类:
andand
andand uses a more nefarious technique, hacking the fact that you can't directly instantiate NilClass subclasses:
class Object
def andand
if self
self
else # this branch is chosen if `self.nil? or self == false`
Mock.new(self) # might want to modify if you have useful methods on false
end
end
end
class Mock < BasicObject
def initialize(me)
super()
@me = me
end
def method_missing(*args) # if any method is called return the original object
@me
end
end
这使您可以通过以下方式进行编程:
This allows you to program this way:
fizz.andand.buzz.andand.foo.andand.bar
结合一些花哨的重写
同样,您可以扩展此技术:
Combine with some fancy rewriting
Again you could expand on this technique:
class Object
def method_missing(m, *args, &blk) # `m` is the name of the method
if m[0] == '_' and respond_to? m[1..-1] # if it starts with '_' and the object
Mock.new(self.send(m[1..-1])) # responds to the rest wrap it.
else # otherwise throw exception or use
super # object specific method_missing
end
end
end
class Mock < BasicObject
def initialize(me)
super()
@me = me
end
def method_missing(m, *args, &blk)
if m[-1] == '_' # If method ends with '_'
# If @me isn't nil call m without final '_' and return its result.
# If @me is nil then return `nil`.
@me.send(m[0...-1], *args, &blk) if @me
else
@me = @me.send(m, *args, &blk) if @me # Otherwise call method on `@me` and
self # store result then return mock.
end
end
end
要解释发生了什么:调用带下划线的方法时,您会触发模拟模式,_meth
的结果会自动包装在Mock
对象中.每当您在该模拟程序上调用方法时,它都会检查其是否不包含nil
,然后将您的方法转发到该对象(此处存储在@me
变量中).然后,模拟将用函数调用的结果替换原始对象.当您调用meth_
时,它将结束模拟模式并返回meth
的实际返回值.
To explain what's going on: when you call an underscored method you trigger mock mode, the result of _meth
is wrapped automatically in a Mock
object. Anytime you call a method on that mock it checks whether its not holding a nil
and then forwards your method to that object (here stored in the @me
variable). The mock then replaces the original object with the result of your function call. When you call meth_
it ends mock mode and returns the actual return value of meth
.
这允许使用这样的api(我使用下划线,但是您可以使用任何东西):
This allows for an api like this (I used underscores, but you could use really anything):
fizz._buzz.foo.bum.yum.bar_
残酷的猴子修补方法
这确实很讨厌,但是它允许使用优雅的API,并不一定会破坏整个应用程序中的错误报告:
Brutal monkey-patching approach
This is really quite nasty, but it allows for an elegant API and doesn't necessarily screw up error reporting in your whole app:
class NilClass
attr_accessor :complain
def method_missing(*args)
if @complain
super
else
self
end
end
end
nil.complain = true
像这样使用:
nil.complain = false
fizz.buzz.foo.bar
nil.complain = true