为什么`Kernel :: String`检查`to_str`结果而`Kernel :: Integer`不检查`to_int`结果?

时间:2015-01-02 18:56:56

标签: ruby

Kernel::IntegerKernel::String首先尝试调用“long”方法(分别为to_intto_str),然后是“简短”方法(分别为to_ito_str。两种方法都检查“短”方法结果的类,并在需要时引发错误:

[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)

这种行为似乎是合乎逻辑的,因为“短”方法应该简单地给出“表示”。另一方面,只有当所讨论的对象本质上是整数或字符串(see this answer)时,才应该实现“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"

1 个答案:

答案 0 :(得分: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;
    
  5. 因此,它检查to_int的返回值以查看它是否为零,而不是它是否为整数。我在该代码中评论的行是导致此错误的行。在上面的to_i调用中检查rb_to_integer的结果类型(如果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