Что мешает мне включить класс в Ruby? - PullRequest
3 голосов
/ 16 января 2020

Я пытаюсь понять некоторые Ruby внутренние компоненты:

Попытка include класса вместо модуля , приводит к TypeError: (это специально)

class C
end

class Foo
end

Foo.include(C)
#=> TypeError: wrong argument type Class (expected Module)

Я хотел бы знать, как эта проверка типа работает "под капотом".

Поскольку классы являются модулями, я Предполагается, Ruby проверяет, является ли аргумент фактическим экземпляром Module:

C.is_a?(Module)        #=> true
C.instance_of?(Module) #=> false

Звучит разумно, не правда ли?

Но когда я определяю свой собственный Module подкласс и создать экземпляр этого подкласса, он работает просто отлично:

class Klass < Module
end

K = Klass.new

Foo.include(K)
# no error

Но K - это экземпляр Klass, точно так же, как C - это экземпляр Class. И Klass является подклассом Module, так же как Class:

K.is_a?(Module)        #=> true
K.instance_of?(Module) #=> false

K.class #=> Klass
C.class #=> Class

Klass.superclass #=> Module
Class.superclass #=> Module

Так что же на самом деле делает проверка типов в include?

Есть ли скрытый свойство, которое позволяет Ruby отличать модули от классов?

Так как это спецификация реализации c: меня особенно интересует YARV / MRI.

Ответы [ 2 ]

4 голосов
/ 16 января 2020

Как прокомментировал @Stefan, Module#include вызывает макрос Check_Type(module, T_MODULE). Вы можете найти это в https://ruby-doc.org/core-2.6/Module.html#method -i-include

Дальнейшее копание исходного кода вы можете найти в файле заголовка ruby .h , есть строка

#define Check_Type(v,t) rb_check_type((VALUE)(v),(t))

, поэтому Check_Type это просто удобный псевдоним rb_check_type, и вы можете найти определение rb_check_type в error. c:

void
rb_check_type(VALUE x, int t)  
{ 
    int xt;                    

    if (x == Qundef) {         
  rb_bug(UNDEF_LEAKED);        
    }

    xt = TYPE(x);              
    if (xt != t || (xt == T_DATA && RTYPEDDATA_P(x))) {
  unexpected_type(x, xt, t);   
    }
} 

int t - это уникальный «идентификатор» для типа, а int xt - это идентификатор фактического типа x. Вы можете видеть if (xt != t || ...), поэтому Check_Type проверяет эквивалентность типов, а не отношение is-a.

TL; DR

Ruby проверяет, является ли включенный модуль действительно модуль а не класс.

2 голосов
/ 16 января 2020

Я отвечаю на свой вопрос здесь


Есть ли скрытое свойство, которое позволяет Ruby отличать модули от классов?

Действительно, есть. Внутри все Ruby объекты начинаются со структуры с именем RBasic:

struct RBasic {
    VALUE flags;
    const VALUE klass;
};

В пределах RBasic у нас есть flags, и эти флаги содержат информацию о типе:

enum ruby_value_type {
    RUBY_T_NONE = 0x00,

    RUBY_T_OBJECT = 0x01,
    RUBY_T_CLASS = 0x02,
    RUBY_T_MODULE = 0x03,
    RUBY_T_FLOAT = 0x04,
    RUBY_T_STRING = 0x05,
    // ...

    RUBY_T_MASK = 0x1f
};

И вот что в конечном итоге проверяет Ruby при выполнении проверки типа: Marshal также используется *1023* для вывода информации о типе:

module M ; end
class C ; end

Marshal.dump(M) #=> "\x04\bm\x06M"
Marshal.dump(C) #=> "\x04\bc\x06C"
Marshal.dump(4) #=> "\x04\bi\t"
#                          ^
#              m = module, c = class, i = integer

Изнутри Ruby мы можем проверить внутренний тип с помощью Fiddle :

require 'fiddle'

def type(obj)
  struct = Fiddle::Pointer.new(obj.object_id << 1)
  flags = struct[0]
  flags & 0x1f
end

module M ; end
class C ; end

type(M) #=> 3   (RUBY_T_MODULE = 0x03)
type(C) #=> 2   (RUBY_T_CLASS = 0x02)

И поскольку Fiddle также позволяет изменять базовые данные, мы, вероятно, могли бы превратить класс в модуль, изменив его Соответствующие флаги ...

Давайте попробуем: image

class C
  def hello
    'hello from class'
  end
end

class Foo
end

Foo.include(C)
#=> TypeError: wrong argument type Class (expected Module)

Теперь тип меняется с 0x02 (класс) на 0x03 (модуль):

require 'fiddle'

struct = Fiddle::Pointer.new(C.object_id << 1)
struct[0] = (struct[0] & ~0x1f) | 0x03

Foo.include(C)
# NoMethodError: undefined method `append_features' for C:Class

Все еще ошибка, но Ruby больше не жалуется на тип!

Очевидно, Class не определяет Module#append_features, потому что метод не имеет большого смысла для классов , Давайте переопределим его для C:

C.define_singleton_method(:append_features, Module.instance_method(:append_features))

Foo.include(C)
# no error!

Foo.ancestors
#=> [Foo, C, Object, BasicObject, Object, Kernel, BasicObject]

Foo.new.hello
#=> "hello from class"

И вот мы go: класс, включенный в другой класс.

Примечание: я возился с внутренностями Ruby Вот. Не используйте этот вид хаков в производстве. Вы были предупреждены.

...