Ruby像nil的对象 [英] Ruby nil-like object

查看:96
本文介绍了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

如果fizzbuzz属性不可用,我将返回我的类似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

这篇关于Ruby像nil的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆