为什么我不能在模块中扩展Fixnum类并使用它?

nar*_*ero 2 ruby module class extend

我创建了一个模块,在其中我使用新方法扩展了Fixnum类.但是当我require模块并尝试使用扩展方法时,它返回:

NoMethodError: undefined method `roundup' for 13:Fixnum
Run Code Online (Sandbox Code Playgroud)

这是我的模块的样子:

module EanControl
  # Extend Fixnum with #roundup
  class Fixnum
    def self.roundup
      return self if self % 10 == 0   # already a factor of 10
      return self + 10 - (self % 10)  # go to nearest factor 10
    end
  end

  # More code...
end
Run Code Online (Sandbox Code Playgroud)

这就是我正在做的事情:

require 'path_to_module'
12.roundup

# => NoMethodError: undefined method `roundup' for 13:Fixnum
Run Code Online (Sandbox Code Playgroud)

我该怎么解决这个问题?

Jör*_*tag 11

您的代码有三个问题:

  1. 您正在创建一个EanControl::Fixnum,但实际上您想要更改现有的内置类::Fixnum.解决方案:从顶层显式启动常量查找,或者更惯用地,只是删除模块.

    module EanControl
      class ::Fixnum
        # …
      end
    end
    
    # although it would be much simpler to just do this:
    
    class Fixnum
      # …
    end
    
    Run Code Online (Sandbox Code Playgroud)
  2. 您将其定义roundup为对象的单例方法Fixnum,但您将其称为实例的实例方法Fixnum.解决方案:制作roundup一个实例方法:

    class Fixnum
      def roundup
        return self if (self % 10).zero? # already a factor of 10
        self + 10 - (self % 10)          # go to nearest factor 10
      end
    end
    
    Run Code Online (Sandbox Code Playgroud)
  3. Ruby语言规范实际上并不能保证甚至一个Fixnum类.它只保证有一个Integer类,它允许不同的实现可以提供特定于实现的子类.(例如,YARV具有FixnumBignum子类Integer.)由于您只添加方法Fixnum,因此它不适用于其他Integers,而不是Fixnums.并且由于Fixnums 的范围对于不同的体系结构实现是不同的(例如,在32位系统上的YARV,Fixnums是31位,在64位系统上,它们是63位,在JRuby上,它们总是64位),你不要甚至可以确定您的方法将起作用的数字以及何时失败.(例如:9223372036854775808.roundup # NoMethodError: undefined method 'roundup' for 9223372036854775808:Bignum.)解决方案:使方法成为实例方法Integer:

    class Integer
      def roundup
        return self if (self % 10).zero? # already a factor of 10
        self + 10 - (self % 10)          # go to nearest factor 10
      end
    end
    
    Run Code Online (Sandbox Code Playgroud)

最后,我想建议至少在这里使用mixin:

module IntegerWithRoundup
  def roundup
    return self if (self % 10).zero? # already a factor of 10
    self + 10 - (self % 10)          # go to nearest factor 10
  end
end

class Integer
  include IntegerWithRoundup
end
Run Code Online (Sandbox Code Playgroud)

现在,如果其他人调试你的代码,并想知道这个roundup方法的来源,那么祖先链中有一个明显的痕迹:

12.method(:roundup).owner
# => IntegerWithRoundup
Run Code Online (Sandbox Code Playgroud)

更好的方法是使用精化,这样你的monkeypatch不会污染全局命名空间:

module IntegerWithRoundup
  module Roundup
    def roundup
      return self if (self % 10).zero? # already a factor of 10
      self + 10 - (self % 10)          # go to nearest factor 10
    end
  end

  refine Integer do
    include Roundup
  end
end

12.roundup
# NoMethodError: undefined method `roundup' for 12:Fixnum

using IntegerWithRoundup

12.roundup
# => 20
Run Code Online (Sandbox Code Playgroud)