【问题标题】:Ruby extend NilClassRuby 扩展 NilClass
【发布时间】:2014-01-10 16:13:32
【问题描述】:

我正在尝试为应用程序扩展 Ruby 的 NilClass,以便任何方法调用都将返回 nil(实际上是 self,新扩展的 nil)。目的是避免大量额外的零检查逻辑,例如,

results = data.nil? nil : data.process()
output = results.nil? nil : results.format()

并简单地用

替换它
output = data.process().format()

如果链上的任何地方都返回了 nil 结果,则返回 nil。

让一个类响应任意消息很容易:

class SuperNil
  def method_missing(sym, *args)
    return self
  end
end

现在,supernil = SuperNil.new; supernil.anything 返回对象 supernil。但是,这并不是真正的零,因为它会评估为真。它会以某种方式扩展 NilClass 以便我的对象评估为假吗?我遇到麻烦的地方是,即使在扩展 NilClass 之后,我也无法创建 SuperNil 的对象。 NilClass.new 会报错,SuperNil.new 也会报错。

是否可以创建 NilClass 扩展的对象?它会评估为假吗?

【问题讨论】:

    标签: ruby boolean null


    【解决方案1】:

    试试:

    class NilClass
      def hello
        'hello'
      end
    
      def method_missing *args
        self
      end
    end
    
    nil.hello #=> 'hello'
    nil.non_existing_method #=> nil
    

    然而,这可能是个坏主意,因为undefined method for nil 是最有用的例外之一——它表示你在没有预料到的地方得到了 nil。相反,您应该指定所有您知道可能出现 nil 的地方 - 看看 'andand' gem,它完全符合需要:

    data.nil? nil : data.process()
    
    # is the same as
    data && data.process()
    
    # what justifies the name of the gem
    data.andand.process()
    

    更新:

    为什么不能创建 nil 的子类有一个非常简单的原因 - NilClass 不允许创建新实例,它的 new 方法是未定义的(对于所有不可变的类,如 Fixnum、Symbol,您将遇到同样的问题或 TrueClass)。所以:

    class SuperNil < NilClass
      def method_missing
        self
      end
    end
    

    不会引发任何问题,但您将无法创建此类的任何实例。

    【讨论】:

    • 您的谨慎我认为是不打开 NilClass 的一个重要原因:任何其他可能遇到 nil 的代码(尤其是包含的模块)都不会引发可能必要的异常。我可以看到这很快就会变得危险。像扩展这样的东西可以让我创建一个实例 supernil,我可以将它与 nil 分开使用——甚至覆盖 Array#[] 以返回 supernil 而不是 nil,这样我就可以将数据搅动到我心中的内容而不必担心。我肯定会给自己惹上麻烦。
    • 我已经更新了答案,说明为什么你不能从 NilClass 继承 - 并且 gem 是唯一的方法(好吧,你可以自己写类似的东西,但是如果这工作得刚刚好呢? ?)
    【解决方案2】:

    在您的情况下扩展 NilClass 将无济于事,因为当您编写 nil 时,您可以创建 NilClass 的实例,并且无法覆盖它。这意味着你的类将永远不会被使用。

    您需要修改NilClass 或父类。在 Ruby 中,一切都是对象,包括 nil。然后,您可以修改对象以引入一些自定义行为。

    这是ActiveSupport #try method采用的方法。

    def try(*a, &b)
      if a.empty? && block_given?
        yield self
      else
        __send__(*a, &b)
      end
    end
    

    可以让你缩短

    @person && @person.name
    

    @person ? @person.name : nil
    

    进入

    @person.try(:name)
    

    我的建议是不要滥用这种技术。有时少写代码不一定是好事。

    【讨论】:

      【解决方案3】:

      您是否考虑过只使用辅助方法?例如,而不是

      results = data.nil? nil : data.process()
      output  = results.nil? nil : results.format(1,2)
      

      你可以写

      results = execute(data, :process)
      output  = execute(results, :format, 1, 2)
      

      在哪里

      def execute(receiver, *args)
        return nil unless receiver
        receiver.send(*args)
      end
      

      两个例子:

      a = [1,2,3]
      b = nil
      execute(a, :max)       # => 3
      execute(b, :max)       # => nil
      
      execute(a, :delete, 2) # => 2, a => [1,3]
      execute(b, :delete, 2) # => nil
      

      【讨论】:

        【解决方案4】:

        您可以使用 Ruby 2.3.0 中引入的safe navigation operator

        output = data&.process&.format
        

        同样,您可以使用Hash#dig,同样在 Ruby 2.3.0 中引入。

        output = data.dig(:process, :format)
        

        【讨论】:

          猜你喜欢
          • 2013-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-07
          • 2011-03-25
          • 2018-12-15
          相关资源
          最近更新 更多