ruby 为什么单例方法不能在符号或Fixnums上定义?

bjp0bcyl  于 2023-03-08  发布在  Ruby
关注(0)|答案(2)|浏览(84)

有一些Ruby类不允许在它们的示例上定义单例方法,例如Symbol

var = :asymbol

def var.hello
  "hello"
end

# TypeError: can't define singleton method "hello" for Symbol
  • 我认为这可能是对所有立即数的限制,但它似乎适用于niltruefalse*(但不适用于FixnumBignum的示例):
var = true

def var.hello
  "hello"
end

var.hello #=> "hello"

我不明白为什么Ruby允许在某些对象类上定义单例方法,而在其他对象类上却不允许。

gt0wga4j

gt0wga4j1#

这与Matz在这里描述的“立即值”概念有关。
实际上,立即值不允许使用单例方法,但是,在truefalsenil的情况下,实际上存在支持这些值的单例类(或者值实际上是单例类--我不确定这一点)因此,您可以向后台类添加单例示例,使其表现为值本身。Numeric和Symbol示例不是单例(很明显),没有地方容纳单例方法。

vh0rcniy

vh0rcniy2#

目前(ruby-3.2.1)还不能为整数、浮点数、符号和冻结字符串定义单例方法(我也用ruby-2.6.10确认了这一点):

v = Object.new
v = nil
v = false
v = true
v = 'string'
v = 1                       #=> TypeError: can't define singleton
v = 1000000000000000000000  #=> TypeError: can't define singleton
v = 1.0                     #=> TypeError: can't define singleton
v = 1e100                   #=> TypeError: can't define singleton
v = :sym                    #=> TypeError: can't define singleton
v = :"sym#{v}"              #=> TypeError: can't define singleton
v = -'string'               #=> TypeError: can't define singleton
def v.m() end

代码的相应部分如下所示:

static VALUE
singleton_class_of(VALUE obj)
{
    VALUE klass;

    switch (TYPE(obj)) {
      case T_FIXNUM:
      case T_BIGNUM:
      case T_FLOAT:
      case T_SYMBOL:
        rb_raise(rb_eTypeError, "can't define singleton");

      case T_FALSE:
      case T_TRUE:
      case T_NIL:
        klass = special_singleton_class_of(obj);
        if (NIL_P(klass))
            rb_bug("unknown immediate %p", (void *)obj);
        return klass;

      case T_STRING:
        if (FL_TEST_RAW(obj, RSTRING_FSTR)) {
            rb_raise(rb_eTypeError, "can't define singleton");
        }
    }
    ...

现在,让我们看看TYPE的功能:

#define TYPE(_)           RBIMPL_CAST((int)rb_type(_))

rb_type()

static inline enum ruby_value_type
rb_type(VALUE obj)
{
    if (! RB_SPECIAL_CONST_P(obj)) {
        return RB_BUILTIN_TYPE(obj);
    }
    else if (obj == RUBY_Qfalse) {
        return RUBY_T_FALSE;
    }
    else if (obj == RUBY_Qnil) {
        return RUBY_T_NIL;
    }
    else if (obj == RUBY_Qtrue) {
        return RUBY_T_TRUE;
    }
    else if (obj == RUBY_Qundef) {
        return RUBY_T_UNDEF;
    }
    else if (RB_FIXNUM_P(obj)) {
        return RUBY_T_FIXNUM;
    }
    else if (RB_STATIC_SYM_P(obj)) {
        return RUBY_T_SYMBOL;
    }
    else {
        RBIMPL_ASSUME(RB_FLONUM_P(obj));
        return RUBY_T_FLOAT;
    }
}

信息量很大,我先给你们一些常数:

RUBY_Qfalse         = 0x00, /* ...0000 0000 */
    RUBY_Qnil           = 0x04, /* ...0000 0100 */
    RUBY_Qtrue          = 0x14, /* ...0001 0100 */
    RUBY_Qundef         = 0x24, /* ...0010 0100 */
    RUBY_IMMEDIATE_MASK = 0x07, /* ...0000 0111 */
    RUBY_FIXNUM_FLAG    = 0x01, /* ...xxxx xxx1 */
    RUBY_FLONUM_MASK    = 0x03, /* ...0000 0011 */
    RUBY_FLONUM_FLAG    = 0x02, /* ...xxxx xx10 */
    RUBY_SYMBOL_FLAG    = 0x0c, /* ...xxxx 1100 */

和一些类型常量:

RUBY_T_FLOAT    = 0x04, /**< @see struct ::RFloat */
    RUBY_T_STRING   = 0x05, /**< @see struct ::RString */
    RUBY_T_BIGNUM   = 0x0a, /**< @see struct ::RBignum */
    RUBY_T_NIL      = 0x11, /**< @see ::RUBY_Qnil */
    RUBY_T_TRUE     = 0x12, /**< @see ::RUBY_Qfalse */
    RUBY_T_FALSE    = 0x13, /**< @see ::RUBY_Qtrue */
    RUBY_T_SYMBOL   = 0x14, /**< @see struct ::RSymbol */
    RUBY_T_FIXNUM   = 0x15, /**< Integers formerly known as Fixnums. */

那么,什么是即时价值呢?
RB_IMMEDIATE_P

static inline bool
RB_IMMEDIATE_P(VALUE obj)
{
    return obj & RUBY_IMMEDIATE_MASK;
}

RB_SPECIAL_CONST_P

static inline bool
RB_SPECIAL_CONST_P(VALUE obj)
{
    return RB_IMMEDIATE_P(obj) || obj == RUBY_Qfalse;
}

代码中的名称似乎有点误导,但下面的方式或多或少是有意义的。立即值是设置了3个最低有效位中的任何一个和RUBY_Qfalse的值。一些立即值是常量,如nil0x04),false0x00),true0x14).有些只是内联存储值(或者至少不指向某个地方),比如fixnums(整数),flonums(浮点数)和静态符号。
RB_FIXNUM_P(是否为修复号?):

static inline bool
RB_FIXNUM_P(VALUE obj)
{
    return obj & RUBY_FIXNUM_FLAG;
}

RB_FLONUM_P(是否为氟?):

static inline bool
RB_FLONUM_P(VALUE obj)
{
#if USE_FLONUM
    return (obj & RUBY_FLONUM_MASK) == RUBY_FLONUM_FLAG;
#else
    return false;
#endif
}

RB_STATIC_SYM_P(是否为静态符号?):

static inline bool
RB_STATIC_SYM_P(VALUE obj)
{
    RBIMPL_ATTR_CONSTEXPR(CXX14)
    const VALUE mask = ~(RBIMPL_VALUE_FULL << RUBY_SPECIAL_SHIFT);
    return (obj & mask) == RUBY_SYMBOL_FLAG;
}

RB_BUILTIN_TYPE(返回指针指向的值的非立即数类型):

static inline enum ruby_value_type
RB_BUILTIN_TYPE(VALUE obj)
{
    ...
    VALUE ret = RBASIC(obj)->flags & RUBY_T_MASK;
    return RBIMPL_CAST((enum ruby_value_type)ret);
}

RBASIC(类型转换为struct RBasic*,这是一个符合任何VALUE的结构体,不是指针):

#define RBASIC(obj)                 RBIMPL_CAST((struct RBasic *)(obj))

那么FL_TEST_RAW(obj, RSTRING_FSTR)呢?这是一种检查字符串是否冻结的方法。
现在,实际上一些整数、浮点数和符号是以指针的形式存储的,这就是为什么我对每种类型都进行了两次检查:1(立即数)和1000000000000000000000(指针),1.0(立即数)和1e100(指针),:sym(立即数)和:"sym#{v}"(指针,动态符号)。在ruby-2.6中,区别更为明显。
至于为什么你可以给nil添加方法,而不能给整数添加方法?难倒我了:)很可能是因为nil是一个,而整数有很多。目前,由ruby来选择是内联还是在对象中存储一个值。如果你可以给整数、浮点数和符号添加单例方法,那么ruby将被迫把它们存储为对象(那些有额外方法的对象)。这就消除了优化。可能被认为弊大于利。
顺便说一下,-'string'是一个冻结的字符串。

相关问题