- Имеют ли
obj1
и obj2
в коде Ruby каждый экземпляр метода some_method
? Или это похоже на JavaScript, где оба объекта имеют доступ к some_method
через другой объект (в данном случае через класс Child)?
Вы не знаете. Спецификация языка Ruby просто говорит: «Если вы делаете это, происходит то». Однако он не предписывает какой-либо конкретный способ, чтобы это произошло. Каждая реализация Ruby может реализовывать ее так, как считает нужным, пока результаты совпадают со спецификацией, спецификации не важно, как были получены эти результаты.
Вы не можете сказать. Если реализация поддерживает правильную абстракцию, вы не сможете сказать, как они это делают. Такова природа абстракции. (На самом деле это почти определение абстракции.)
- Точно так же, когда в Ruby учитывается наследование, имеет ли каждый объект Ruby копию всех одноименных методов класса и суперкласса?
То же, что и выше.
В настоящее время существует много реализаций Ruby, а в прошлом их было еще больше, на разных стадиях (не)завершенности. Некоторые из них реализуют свои собственные объектные модели (например, MRI, YARV, Rubinius, MRuby, Topaz, tinyrb, RubyGoLightly), некоторые располагаются поверх существующей объектной модели, в которую они пытаются вписаться (например, XRuby и JRuby на Java, Ruby.NET и IronRuby в CLI, SmallRuby, smalltalk.rb, Alumina и MagLev в Smalltalk, MacRuby и RubyMotion в Objective-C/Cocoa, Cardinal в Parrot, Red Sun в ActionScript/Flash, BlueRuby в SAP/ABAP , HotRuby и Opal.rb на ECMAScript)
Кто сказал, что все эти реализации работают одинаково?
Моя интуиция подсказывает мне, что объекты Ruby НЕ имеют отдельные копии методов, унаследованных от их класса, смешанных модулей и суперклассов. Вместо этого я считаю, что Ruby обрабатывает поиск метода аналогично JavaScript, где объекты проверяют, есть ли у самого объекта метод, и если нет, он ищет метод в классе объекта, смешанных модулях и суперклассах, пока поиск не достигнет BasicObject
.
Несмотря на то, что я написал выше, это является разумным предположением, и на самом деле это то, как работают известные мне реализации (MRI, YARV, Rubinius, JRuby, IronRuby, MagLev, Topaz).
Просто подумайте, что бы это значило, если бы это было не так. Каждый экземпляр класса String
должен иметь собственную копию всех 116 методов String
. Подумайте, сколько String
в типичной программе Ruby!
ruby -e 'p ObjectSpace.each_object(String).count'
# => 10013
Даже в этой простейшей программе, которая не использует require
никаких библиотек и сама создает только одну строку (для вывода числа на экран), уже содержится более 10000 строк. Каждый из них будет иметь свои собственные копии более 100 методов String
. Это было бы огромной тратой памяти.
Это также был бы кошмар синхронизации! Ruby позволяет вам в любое время изменять методы. Что, если я переопределю метод в классе String
? Теперь Ruby придется обновлять каждую копию этого метода, даже в разных потоках.
И на самом деле я учитывал только общедоступные методы, определенные непосредственно в String
. С учетом приватных методов количество методов еще больше. И, конечно же, есть наследование: строкам потребуется не только копия каждого метода в String
, но и копия каждого метода в Comparable
, Object
, Kernel
и BasicObject
. Можете ли вы представить, что каждый объект в системе имеет копию require
?
Нет, так это работает в большинстве реализаций Ruby. У объекта есть идентификатор, переменные экземпляра и класс (в псевдо-Ruby со статическим типом):
struct Object
object_id: Id
ivars: Dictionary<Symbol, *Object>
class: *Class
end
Модуль имеет словарь методов, словарь констант и словарь переменных класса:
struct Module
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
end
Класс подобен модулю, но у него также есть суперкласс:
struct Class
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
superclass: *Class
end
Когда вы вызываете метод для объекта, Ruby ищет указатель объекта class
и пытается найти там метод. Если это не так, он будет смотреть на указатель класса superclass
и так далее, пока не достигнет класса, у которого нет суперкласса. В этот момент он на самом деле не сдастся, а попытается вызвать метод method_missing
для исходного объекта, передав имя метода, который вы пытались вызвать, в качестве аргумента, но это тоже обычный вызов метода, поэтому он следует всем те же правила (за исключением того, что если вызов method_missing
достигает вершины иерархии, он не будет пытаться вызвать его снова, что приведет к бесконечному циклу).
О, но мы проигнорировали одну вещь: одноэлементные методы! Каждый объект также должен иметь свой собственный словарь методов. На самом деле, скорее, у каждого объекта есть свой собственный класс singleton в дополнение к своему классу:
struct Object
object_id: Id
ivars: Dictionary<Symbol, *Object>
class: *Class
singleton_class: Class
end
Таким образом, поиск метода начинается сначала в классе синглтона и только затем переходит к классу.
А как же миксины? Ах да, каждому модулю и классу тоже нужен список включенных миксинов:
struct Module
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
mixins: List<*Module>
end
struct Class
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
superclass: *Class
mixins: List<*Module>
end
Теперь алгоритм следующий: посмотрите сначала в одноэлементном классе, затем в классе, а затем в суперклассе (ах), где, однако, «смотреть» также означает «после того, как вы просмотрите словарь методов, также просмотрите все словари методов класса». включенные примеси (и включенные примеси включенных примесей и т. д., рекурсивно) перед переходом к суперклассу".
Звучит сложно? Это! И это нехорошо. Поиск метода — единственный наиболее часто выполняемый алгоритм в объектно-ориентированной системе, он должен быть простым и молниеносным. Итак, то, что делают некоторые реализации Ruby (например, MRI, YARV), заключается в том, чтобы отделить внутреннее представление интерпретатора о том, что означают «класс» и «суперкласс», от взгляда программиста на те же самые концепции.
У объекта больше нет и одноэлементного класса, и класса, у него просто есть класс:
struct Object
object_id: Id
ivars: Dictionary<Symbol, *Object>
class: *Class
singleton_class: Class
end
У класса больше нет списка включенных примесей, только суперкласс. Однако это может быть скрыто. Обратите также внимание, что словари становятся указателями, и вы вскоре поймете, почему:
struct Class
methods: *Dictionary<Symbol, *Method>
constants: *Dictionary<Symbol, *Object>
cvars: *Dictionary<Symbol, *Object>
superclass: *Class
visible?: Bool
end
Теперь указатель класса объекта всегда будет указывать на класс-одиночку, а указатель надкласса класса-одиночки всегда будет указывать на фактический класс объекта. Если вы включите примесь M
в класс C
, Ruby создаст новый невидимый класс M′
, который будет использовать свой метод, константы и словари переменных вместе с примесью. Этот класс миксина станет суперклассом C
, а старый суперкласс C
станет суперклассом класса миксина:
M′ = Class.new(
methods = M->methods
constants = M->constants
cvars = M->cvars
superclass = C->superclass
visible? = false
)
C->superclass = *M'
На самом деле, это немного сложнее, так как он также должен создавать классы для миксинов, которые включены в M
(и рекурсивно), но в итоге мы получаем хороший линейный путь поиска метода без обходных шагов. в одноэлементные классы и включены миксины.
Алгоритм поиска метода таков:
def lookup(meth, obj)
c = obj->class
until res = c->methods[meth]
c = c->superclass
raise MethodNotFound, meth if c.nil?
end
res
end
Красиво и чисто, скудно и быстро.
В качестве компромисса, узнать класс объекта или суперкласс класса немного сложнее, потому что вы не можете просто вернуть указатель класса или суперкласса, вы должны пройти по цепочке, пока не найдете класс, который не спрятанный. Но как часто вы звоните Object#class
или Class#superclass
? Вы вообще его вызываете, кроме отладки?
К сожалению, Module#prepend
не очень хорошо вписывается в эту картину. А уточнения на самом деле все портят, поэтому многие реализации Ruby их даже не реализуют.
person
Jörg W Mittag
schedule
06.02.2015