为什么"内核::字符串"检查"to_str"结果,而"内核::整数"不检查"to_int"结果?



Kernel::IntegerKernel::String首先尝试调用"long"方法(分别为to_intto_str),然后调用"short"方法(分别为to_ito_str)来转换参数。这两种方法都会检查"short"方法结果的类,并在需要时引发错误:

[1] pry(main)> class Dummy
[1] pry(main)*   def to_i
[1] pry(main)*     "42"
[1] pry(main)*   end
[1] pry(main)*   def to_s
[1] pry(main)*     42
[1] pry(main)*   end
[1] pry(main)* end;
[2] pry(main)> Integer(Dummy.new)
TypeError: can't convert Dummy to Integer (Dummy#to_i gives String)
from (pry):9:in `Integer'
[3] pry(main)> String(Dummy.new)
TypeError: can't convert Dummy to String (Dummy#to_s gives Fixnum)

这种行为似乎是合乎逻辑的,因为"短"方法应该只是给出一个"表示"。另一方面,"long"方法应该只在所讨论的对象本质上是整数或字符串时才实现(参见此答案)。

但是,一旦我们实现了"long"方法,行为就会变得不一致:

[4] pry(main)> class Dummy
[4] pry(main)*   def to_int
[4] pry(main)*     "42"
[4] pry(main)*   end
[4] pry(main)*   def to_str
[4] pry(main)*     42
[4] pry(main)*   end
[4] pry(main)* end;
[5] pry(main)> Integer(Dummy.new)
=> "42"
[6] pry(main)> String(Dummy.new)
TypeError: can't convert Dummy to String (Dummy#to_str gives Fixnum)

为什么对结果的处理方式不同?

我正在使用 ruby 2.1.2,顺便说一句:

[7] pry(main)> RUBY_VERSION
=> "2.1.2"

以下是当您调用 Kernel#Integer 时会发生什么,又名 rb_f_integer

  1. rb_f_integer呼叫rb_convert_to_integer
  2. rb_convert_to_integer呼叫convert_type(val, "Integer", "to_int", FALSE)
  3. convert_type返回val.to_int,无论它是否实际上是整数。
  4. rb_convert_to_integer的重要部分是:

    tmp = convert_type(val, "Integer", "to_int", FALSE);
    if (NIL_P(tmp)) { // checks if val.to_int is nil. this is the line that causes this
        return rb_to_integer(val, "to_i");
    }
    return tmp;
    

因此,它检查 to_int 的返回值以查看它是否为 nil,而不是它是否是整数。我在该代码中注释的行是导致此错误的行。在上述对rb_to_integer的调用中检查了to_i结果的类型(如果to_int的结果为 nil 或未定义to_int),但从不检查to_int结果的类型。这样做的一个有趣的结果是:

class X
  def to_int
    nil
  end
  def to_i
    42
  end
end
class Y
  def to_int
    false
  end
  def to_i
    42
  end
end
p Integer(X.new) #=> 42
p Integer(Y.new) #=> false

最新更新