The Ruby Object Model

Data structure in detail

A semi-formal description of the Ruby object model is presented. Data structure of the Ruby programming language is described in series of state transition systems, each built atop of the previous one(s). As a result, a nomenclature is proposed (including such notions as class, eigenclass, metaclass, module, includer or method resolution order) together with the semantics.

Preface

Author
Ondřej Pavlata
Jablonec nad Nisou
Czech Republic
Document date
Initial releaseMarch 2, 2011
Last major release June 21, 2012
Last updateOctober 10, 2012
(see Document history)
Warning
  1. This document has been created without any prepublication review except those made by the author himself.
  2. The author is not an experienced Ruby programmer. In fact, he did not write any Ruby program except very simple tests.
  3. Most of the author's experience with dynamic programming languages comes from JavaScript.
Ruby version
This document refers to Ruby 1.9, more specifically, to the Matz's Ruby Interpreter (MRI) 1.9.2.

Table of contents

Preliminaries

Some familiarity with elementary algebra and order theory is assumed. This involves such notions as structure, substructure, sort, generating set/structure, (partial) function, relation, domain, range, restriction, extension, injectivity, reflexivity, transitivity, monotonicity, isomorphism, (order) chain, closure operator.
Monounary algebra
A (total) monounary algebra (aka functional graph []) is an algebra with a single unary operation, i.e. it is a structure (X, .p¯) such that A partial monounary algebra is a structure (X, .p) where .p is a partial function on X (i.e. .p : X X). An element x X is called a fixed point if x.p = x. There is a one-to-one correspondence between monounary algebras and partial monounary algebras without fixed points: the fixed points become undefined and vice versa. This can be notationally expressed by adding/removing an overline (.p¯ ↔ .p).

We denote x.p(i) the i-th application of .p to x (we put x.p(0) = x). A subset C X is called a

A (partial) monounary algebra (X, .p) is connected if X itself is a component.

A structure is said to be locally finite if its every finitely generated substructure is finite. For a (partial) monounary algebra (X, .p) it means that for every x X, the set {x, x.p(1), x.p(2), …, } is finite.

Proposition: Let (X, .p¯) be a connected (total) monounary algebra.

  1. Either (a) or (b) occurs:
  2. The case (a) occurs iff (X, .p¯) is locally finite.
    (In particular, every finite connected monounary algebra has exactly one cycle.)
Pseudotree
We call a monounary algebra (X, .p¯) a pseudotree if it is connected and locally finite. Equivalently (by the proposition from the previous subsection), X contains exactly one cycle C and no ω-chains. We call C the pseudoroot.

The following picture shows a pseudotree with a 4-element pseudoroot C. An arrow x y means x.p¯ = y.

The brown arrow indicates that after choosing an r C and a redefinition of .p¯ to .p¯r by we obtain a structure (X, .p¯r, r) that is an algebraic tree.
Algebraic forest
By an algebraic forest (or just forest) we mean a structure which has one of the following equivalent forms:
  1. (1) A partial order whose every principal up-set is a finite chain:
    A partial order (X, ) such that for every x X, the set x.ps = { y X | x y } is finite and totally ordered by .
  2. (2) A monounary algebra whose every non-empty subalgebra has a fixed point:
    An algebra (X, .p¯) with just one unary operator, .p¯ : X X, such that for every x X, x.p¯(i) = x.p¯(i+1) for some natural i.
  3. (3) A partial monounary algebra without total non-empty subalgebras:
    A partial algebra (X, .p) with just one partial unary operator, the parent partial function .p: X X, such that for every x X, x.p(i) is defined for only finitely many i.
Because the set x.ps from (1) is a finite chain in , we can regard it as a finite list. Then the correspondence (1)↔(2)↔(3) is established as follows:
(2)←(1)
  • x.p¯(i) = x.ps[i] if i x.ps.length,
  • x.p¯(i) = x.ps.last otherwise,
(1)←(2)
  • x y iff x.p¯(i) = y for some i,   i.e.
i.e. .p¯ is the smallest function on X (w.r.t. ) satisfying (), (X,) is the reflexive transitive closure of (X, .p¯), ()


(3)←(1)
  • x.p(i) = x.ps[i] if i x.ps.length - 1,
  • x.p(i) is undefined otherwise.
(1)←(3)
  • x y iff x.p(i) = y for some i,   i.e.
i.e. .p is the smallest relation on X satisfying (), the
reflexive transitive reduction of (X,) (a.k.a Hasse diagram),
(X,) is the reflexive transitive closure of (X, .p) ().

The root map .r: X X is defined by x.r = x.ps.last. Obviously, .r is a closure operator w.r.t. (X, ). An element x is a root if it satisfies any of the following equivalent conditions:
  1. x is .r-closed,
  2. x is a fixed point w.r.t. .p¯,
  3. x has undefined parent x.p.

Notes:

  1. We consider the partial order form (1) as the primary one so that we usually prepend the phrase reflexive transitive closure of when referring to algebraic forests of form (2) or (3).
  2. The form (3) shows that an algebraic forest can be viewed as a special case of a directed acyclic graph (DAG) which in turn is a special case of a digraph (a directed graph). In this context the term algebraic forest is equivalent to that of a rooted forest [].
Algebraic tree
By an algebraic tree (or just tree) we mean an algebraic forest with exactly one root. As an algebra, it is a structure (X, .p¯, r), such that
  1. (X, .p¯) is an algebraic forest,
  2. r is the only fixed point of .p¯.

Proposition:

A structure (X, .p¯, r) is an algebraic tree iff (X, .p¯) is a pseudotree with the pseudoroot being the singleton set {r}.

Primorder algebra
By a primorder algebra we mean a structure (X, .ec, .pr) where Elements from the range X.pr are primary. Elements from the range X.ec are eigenclasses. The structure is subject to the following axioms: We write x.ec(i) for i-th application of .ec to x. The eigenclass index of x, denoted x.eci, is defined as the depth of x in (X, .ce), i.e. it is the unique i such that x.pr.ec(i) = x.

Proposition:

  1. Each component of the monounary algebra (X, .ec) is isomorphic (via .eci) to the structure (, succ) of natural numbers where succ is the successor operator.
  2. X = X.pr X.ec, i.e. an element is either primary or an eigenclass.
  3. For an element x the following are equivalent:
  4. A primorder algebra (X, .ec, .pr) is uniquely given by its reduct (X, .ec).
 
State transition system
By a state transition system we mean structure (C, Act, T, r) where The reachability relation, R*, is defined as the reflexive transitive closure of the natural projection R of T to C × C. The class of reachable states is a subclass D of C that equals the image of the initial state r under R*.

Data structure description pattern

We will describe Ruby program data structure as a system of systems of structures. We might think of this pattern as of a two-dimensional evolution of algebraic structures:
  1. The outer evolution refers to our incremental description.
  2. The inner evolution is relevant to Gurevich's concept of evolving algebras [] [] [].

Note: We always aim at C being the closest approximation of D as possible so that the data structure is as little determined by transitions as possible.

 

S0: 2x2 nomenclature

Ruby objects can be categorized according to 2 boolean attributes called terminality and primordiality. Based on terminality, objects can be either terminative or classive. Based on primordiality, objects can be either primary or secondary. The following table shows most of the nomenclature related to this categorization.
primordiality →
Primary
objects
 
Secondary objects


 terminality ↓
Classive objects
alias
Classives
Classes
Eigenclasses
Terminative objects
alias
Terminatives
Terminals
We avoid using the term instances for terminals because we would have to accept one of the following statements: We avoid using the term singleton classes for eigenclasses or for terminative eigenclasses because otherwise we would have to accept the following:
We formalize the 2x2 nomenclature as the S0 structure: an S0 structure is a structure (O, .terminative?, .primary?) where

S1: Inheritance and primorder

An S1 structure is a structure (O, .ec, .pr, .sc, r, c) where We introduce additional notation and terminology. The structure is subject to the following axioms:
(S1~1) The structure (O, .ec, .pr) is a primorder algebra.
We will apply established notation and terminology, in particular, definition of .ce and .eci.
(S1~2) The inheritance is an algebraic tree on non-terminals, its root is r. More specifically, for every object x,
  • x.sc is defined iff x is neither the inheritance root r nor a terminal,
  • if x.sc is defined, then x.sc(n) equals r for some, necessarily unique, n.
(S1~3) .sc and .ec are commutative in the following sense:
if x.sc is defined, then x.ec.sc is defined and is equal to x.sc.ec.
(.sc-links per .ec-chains are parallel.)
(S1~4) .sc preserves primary objects on non-terminals, i.e. x.sc is a class for every non-root class x.
(S1~5) .ec.sc preserves primary objects on terminals, i.e. x.ec.sc is primary for every terminal x.
(S1~6) .sc maps to classives, i.e. for every object x, if x.sc is defined then x.sc.pr is a class.
(S1~7) c equals r.ec.sc and is different from r (this prevents the degenerate case of r being the only class).
(S1~8) r.ec is the only object x satisfying x.sc == c.

Proposition:

  1. c is a class.
  2. Classes form an algebraic subtree of the inheritance tree, i.e.
  3. Classes together with first eigenclasses of terminals form an algebraic subtree of the inheritance tree which uniquely determines the inheritance tree.
The subclass-of relation
If x, y are classes such that x.sc(i) == y for some i > 0 then we say that x is a subclass-of y. By a previously mentioned proposition, the reflexive closure of the subclass-of relation is an algebraic tree on classes.
Eigenclass index
Recall that in a primorder algebra, for any object x, the eigenclass index of x, denoted x.eci, is the unique i such that x == x.pr.ec(i).

Proposition: The superclass operator .sc decrements the eigenclass index on (all) eigenclasses of terminals and on (all) eigenclasses of the inheritance root r. On other objects, the index is preserved. I.e. for every object y for which y.sc is defined,

Informally, .sc-links are skew in case (a) and upright in case (b).
Inheritance ancestor lists
For a non-terminal x we denote by x.hancs the list of inheritance ancestors of x. More specifically, .hancs is a partial function from the set O of objects to the set of lists of objects defined by
  1. x.hancs is defined iff x is non-terminal,
  2. x.hancs[i] == y iff x.sc(i) == y.
We also denote the class inheritance-ancestors of a non-terminal x by x.hancestors, i.e. x.hancestors equals x.hancs without eigenclasses.
Metaclasses
An object is called a metaclass if it is an inheritance descendant of c, i.e. a metaclass is a non-terminal object x such that x.hancs contains c. A metaclass is said to be

Note: The explicit/implicit terminology is redundant. We could use the primary/secondary terminology established in the 2x2 nomenclature.

Observations:

Primary inheritance
We denote by .ec_sc_pr the restriction of .ec.sc.pr to the set O.pr {r} of all primary objects except the inheritance root.

Obviously, .ec_sc_pr is an algebraic tree. For every primary x,

We call .ec_sc_pr the primary inheritance.

Note: (*) The .class map is defined later.

The front pseudotree
By restricting .ec.sc.pr to just the set O.pr of all primary objects we obtain the Ruby's front pseudotree. Its pseudoroot is the set of helix classes introduced in the next section. The primary inheritance can be then called the front tree. The picture from the Pseudotree introductory section provides a visualization of these 2 structures.
The S1₀₁ substructure
By an S1₀₁ structure we mean the substructure (O₀₁, .sc, .ec) of an S1 structure such that

Proposition: Up to isomorphism, an S1 structure is uniquely determined by its S1₀₁ substructure.

The Ruby Helix

According to the algebraic definition, the least substructure of a structure S is generated from an empty set using S's constants and functions. Applied to an S1 structure, the least substructure is generated from the inheritance root r using .sc, .ec and .pr.

We call the minimum S1 substructure Ruby helix. The name comes from the fact that the substructure resembles a helical threading of a right-infinite screw. The inheritance corresponds to the helix curve, the primorder corresponds to imaginary lines parallel to the screw axis. This is stated more precisely as follows.

Proposition:

  1. Ruby helix consists of classes c.hancs and their .ec chains. In particular, Ruby helix contains no terminals.
  2. When restricted to the helix, .sc is injective.
  3. Let n be the number of helix classes, i.e. n = c.hancs.length. Then x.ce == x.sc(n) for every helix eigenclass x.
Helix classes
As of version 1.9, Ruby provides just 4 helix classes, forming the following chain:
Class < Module < Object < BasicObject.
In particular, BasicObject is the inheritance root r. The following table shows basic notation and terminology for helix classes.
Document notation Description Ruby name
r the inheritance root BasicObject
¤ the conventional inheritance-root Object
m the metamodule root Module
c the instance root / metaclass root Class

A sample S1 structure

The following table shows a sample restriction of an S1 structure. The .sc function is indicated by arrows and , the .ec function by the arrows. Objects A, B and b are assumed to be created by the code
class A; end; class B < A; end; b = B.new   (cf.[])
Primary objects
(alias classes
and terminals)
Secondary objects
(alias eigenclasses)
Eigenclass index → 0 1 2
(Duplicately presented row) •Class
4-row cylinder skew seam →
Classives •BasicObject
•Object















•Module
•Class
 
•A
•B
Terminatives •b

The .class function alias direct-instance-of relation

The .class function maps objects to classes. For every object x,

Proposition: For every object x, x.class equals the entry-object of the class subtree when traversed from the eigenclass of x, i.e. x.class == x.ec.hancestors[0].

For objects x, y, if x.class == y then we say that y is the class of x and that x is a direct instance of y. The reflexive transitive closure of .class is an algebraic tree with a 2-3 level structure shown in the following table (we use the name Class for the instance root c).

level depth level members
0 (top level) Class
1 Classes except Class Eigenclasses
2 Terminals
Note that such a division is only possible due to (S1~8). This axiom ensures that class Class has no (direct) terminal instances.
The instance-of relation
We define the instance-of relation between objects as the composition .class ◦ . Equivalently,
x is an instance-of y   iff   x.class.sc(i) == y for some i.

Proposition: x is an instance-of y   iff   x.ec.hancestors contains y.

State transitions

The S1 structure is a simplification (or coarsement) of Ruby program data state. Ruby program execution can be abstracted to a (finite or infinite) sequence S0, S1, …, of abstract states or just states. The sequence itself is called an (abstract) run. S0 is the initial state. For each state Si, we denote Si.S1 its corresponding S1 structure.

In addition, we use the following notational conventions: By saying that S → S' is a state transition we mean S and S' are abstract states with S appearing before S' in the run. We use state indices or apostrophes to distinguish between structures for particular states, e.g. Oi denotes the set of objects of Si.S1. We drop underscores whenever no confusion is likely to arise, so that Oi means Oi.

The following rules apply:

(S1-T~1) State transitions are substructural in the S1 structure, i.e. for every states Si, Sj, the restriction of Si.S1 and Sj.S1 to their common object set Oi ∩ Oj is equal. More specifically, for every object x from Oi ∩ Oj,
  • x is terminative in Si iff x is terminative in Sj.
  • ri equals rj.
  • x.sci, x.eci and x.pri are equal to x.scj, x.ecj and x.prj, respectively.
Consequently, transitions preserve the 2x2 nomenclature, eigenclass index, inheritance ancestor lists and the class map.
(S1-T~2) For every consecutive states Si, Si+1, either Oi ⊆ Oi+1 or Oi ⊇ Oi+1.
(S1-T~3) O0 ⊆ Oi for every i, i.e. the initial objects are never removed.
According to (S1-T~2), there are two types of irreducible transitions affecting the S1 structure: increasing (creating new objects) and decreasing.
Creating new objects
This transition adds new primary objects x and their .ec chains to the S1 structure. The following must be specified, explicitly or implicitly, for each such x: In most explicit cases, the transition is accomplished by specifying a class X, different from Class, and then creating a new object x such that x.ec.sc.pr == X either by (a) instantiation of X or (b) subclassing from X, as in the following code:
Removing existing objects
This type of transition is only performed by garbage collection.
Instantiable classes
We say that a class y is instantiable if it is allowed to have instances, i.e. in some reachable state,
Final classes
We say that a class y is final if

Note: The Class class is final (due (S1~8)).

Quasifinal classes
We say that a class y is quasifinal if

Proposition:

  1. Final classes are quasifinal.
  2. Except for Class, quasifinal classes are those that cannot have indirect instances.

Note: The Symbol class is quasifinal (due (S3~4)).

S2: Module inclusion lists

Classes
Metamodules
Modules
Terminals
An S2 structure is an S1 structure equipped with (m, .incs) where Lists from the range of .incs are called (own) inclusion lists. Descendant classes of m different from Class are called metamodules. Terminals that are instances of m are modules.

An S2 structure has the following (additional) axioms:

(S2~1) The root metamodule m equals the already presented helix class Module.
(S2~2) x.incs is defined iff x is a module or a non-terminal.
(S2~3) Only modules can appear in inclusion lists.
(S2~4) A module cannot appear in its own inclusion list.
(S2~5) All inclusion lists are non-repetitive (injective), i.e. no object appears more than once in the same inclusion list.
Includers
We say than an object is an includer if it is a module or a non-terminal. Thus .incs maps includers to lists of includers. Note that this terminology admits includers with no own includees.

Proposition: An object is an includer   iff   it is an instance of Module.

The own-includer-of relation
For includers x, y, if y occurs in x.incs then y is called an own includee of x and x is called an own includer of y. We also say that y is directly included in x.

The reflexive closure, restricted to includers, of the own-includer-of relation is denoted by Μ.

Pure instances
We introduce an additional term for non-includers: pure instances. This means that an object x is a pure instance if it is an end user of the type system: x is neither a class nor an eigenclass nor a module.
The HM descendancy
We define a relation between includers as (Μ) ∪ Μ. Recall that
  1. is the .sc-inheritance, and
  2. Μ is the reflexive closure of the own-includer-of relation.
Equivalently, x ≤ y iff x and y are includers such that at least one of the following is satisfied:
  1. x == y,
  2. x.sc(i) == y for some i,
  3. x.incs[i] == y for some i,
  4. x.sc(i).incs[j] == y for some i, j.
We call this relation HM descendancy or simply descendancy. We use the symbol < for the strict version of (and symbols and > for inverses of and <, respectively).

Proposition:

  1. The restriction of descendancy to non-terminals equals the .sc-inheritance.
  2. Contrary to the semantic convention for the symbol, HM descendancy is not a partial order in general. Neither transitivity nor acyclicity (antisymmetry of the transitive closure) is guaranteed. (See Inclusion anomalies for examples.)
  3. For every includers x, y such that x ≤ y,
The includer-of relation
The includer-of relation is defined as the range-restriction of < to modules, i.e. We also say that y is an includee of x or that y is included in x.

Proposition: The includer-of relation is an extension of the own-includer-of relation.

The kind-of relation
The kind-of relation is defined between all objects as the composition .ec ◦ ≤. Equivalently,

Proposition:

  1. An object cannot be kind of a pure instance.
  2. An object is kind of its eigenclass.
  3. The instance-of relation is a range-restriction of the kind-of relation to classes.

If X is an includer then Xs means the set of objects that are kind of X. This convention is usually applied to named classes or modules.

Notes:

Basic kinds
The following table shows basic kinds of objects using the kind-of relation and helix classes.
kind of BasicObject
Pure instances
(non-includers)
  kind of Object
    kind of Module
Modules
     kind of Class
Classes and Eigenclasses
The Module subtable provides the following nomenclature of includers:
Classes Classes Modules Modules
Classes − Eigenclasses Non-terminals Includers Modules − Classes
Note in particular that this explains the relationship between classes and modules:

Note: Another diagram of the nomenclature is provided as a side view of an example of the S2 structure. This diagram is a refinement of the 2x2 nomenclature table.

Objects versus Objects
Objects that are not kind of Object are considered blank slate objects.
Objects == BasicObjects == Objects ⊎ (blank slate objects).

An S2 structure example

The following picture shows an example of an S2 structure in a 3D perspective.
Front view (slightly rotated)
Legend
  • … helix class
  • … class outside the helix
  • … eigenclass
  • … module
  • … module inclusion copy (iclass)
  • … non-includer (pure instance)
  • … superclass link to a class
  • … superclass link to an eigenclass
  • … eigenclass link
  • … inclusion list link (that cannot be drawn along a superclass link)

Notes:

Side view
Legend See ().

Note: () Each of the 5 division lines partitions the set of objects into two complementary subsets, specified as <set> / <complemetary set>.

The structure can be created as follows.
class S < String; end; class A; end; class B < A; end; class X < Module; end

module R; end; module M; end; N = X.new

s = S.new; i = 20; j = 30; k = 2**70; b = B.new

class BasicObject; include ::R           end
class B;           include M             end
class << B;        include M             end
class X;           include M             end
class << X;        include M             end
module N;          include M, Comparable end
class << s;        include N             end
The code first builds classes (S, A, B, X), then modules (R, M, N), then non-includers (pure instances s, i, j, k, b). Finally, inclusion lists are created.

The diagram shows that inclusion lists refine the sc-inheritance. This refined structure is refered to as MRO in the next section. Ancestor lists in the structure can be reported (without eigenclasses) by the following code ().

class Object; def ec; singleton_class rescue self.class end end

%w{s i j k b    }.each { |x| puts "%s.ec: %s" % [x, eval(x).ec.ancestors] }
%w{B.ec X.ec N X}.each { |x| puts    "%s: %s" % [x, eval(x).ancestors] }
Output
s.ec: [N, M, S, String, Comparable, Object, Kernel, BasicObject, R]
i.ec: [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject, R]
j.ec: [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject, R]
k.ec: [Bignum, Integer, Numeric, Comparable, Object, Kernel, BasicObject, R]
b.ec: [B, M, A, Object, Kernel, BasicObject, R]
B.ec: [M, Class, Module, Object, Kernel, BasicObject, R]
X.ec: [M, Class, Module, Object, Kernel, BasicObject, R]
N: [N, M, Comparable]
X: [X, M, Module, Object, Kernel, BasicObject, R]

Notes about :

The method resolution order (MRO)

MRO domain
We define the method resolution order (MRO) as a relation between elements of Μ. The domain Μ consists of the following two disjoint sets of elements:
  1. Pairs (x,x) with x being an includer (includer elements).
  2. Pairs (x,y) with y being a member of x.incs (iclass elements, y is necessarily a module).
Informally, we just equipped each member of an inclusion list with the context of its includer, making the includee into a unique inclusion class.
MRO .super
The MRO super or MRO parent is a partial function .super defined on the MRO domain Μ as follows:
  1. For pairs (x,x),
    1. (x,x).super == (x.incs[0], x) if x.incs is nonempty, else
    2. (x,x).super == (x.sc, x.sc) if x.sc is defined, else (x,x).super is undefined.
  2. For pairs (x,y) with x different from y,
    1. (x,y).super == (x, x.incs[i+1]) if y equals x.incs[i] for some i < x.incs.length-1, else
    2. (x,y).super == (x.sc, x.sc) if x.sc is defined, else (x,y).super is undefined.
As usual, we denote (x,y).super(i) the ith application of .super to (x,y).
MRO
The method resolution order (Μ, ≤) is defined by
(x,y) ≤ (a,b)  iff 
  • x < a and a is not a module (equivalently, x.sc(i) equals a for some i > 0 ), or
  • x == a and y appears before b in x.incs, or
  • x == a == y.

Proposition:

  1. (x,y) ≤ (a,b)  iff  (x,y).super(i) equals (a,b) for some i, i.e. (Μ, ≤) is a reflexive transitive closure of (Μ, .super).
  2. The method resolution order is an algebraic forest.
  3. MRO consists of two types of components:
    1. (A) There is a unique component containing (r,r), called the MRO tree. Its root equals either (r,r) or (r, r.incs.last).
    2. (B) All the remaining components are chains corresponding to inclusion lists of a module includer extended by the includer itself.
MRO ancestor lists
We denote by .ancs the function from the set of includers to the set of lists of includers defined by
  1. x.ancs[i] == y iff (x,x).super(i) == (w,y) for some w.
If x.ancs[i] is defined then it is said to be the ith (MRO) ancestor of x.

The Ruby .ancestors built-in method filters out eigenclasses: For each class or module x, x.ancestors equals x.ancs without eigenclasses.

Proposition: Let x, y be includers.

  1. x ≤ y iff x.ancs contains y.
  2. (x,y) is in Μ   iff   y is in x.ancs and only modules appear before the first occurrence of y in x.ancs.
  3. (x,y) is in   iff   y is in x.ancs and is not a module.
  4. For any non-terminal x, the inheritance ancestor list x.hancs is obtained from x.ancs by removing modules.
  5. If x is not an eigenclass then x.ancs does not contain an eigenclass (i.e. x.ancs == x.ancestors).
Note that by (2) and (4), an S2 structure can be uniquely specified by a member set with (.sc, .incs) replaced by (.ancs, .module?) where .module? is a boolean attribute indicating whether an object is a module.
A note about terminology
The method resolution order / MRO denomination used for (Μ, ≤) reflects the fact that the relation is used in Ruby's method resolution. However, only the MRO tree is used in this respect. On the other hand, qualified constant resolution uses all components of (Μ, ≤) so that the term qualified constant resolution order / QCRO would be more appropriate. The reason we have chosen MRO is because this term has already been established in the Python programming language [] [].

S2 transitions

The following rules apply:

(S2-T~1) State transitions S → S' are substructural in the S2 structure.
  • The metamodule root m is fixed.
  • Inclusion lists are modified by insertions, i.e. for every class or module x, x.incs is a (possibly non-contiguous) sublist of x.incs'[i].
Consequently, transitions preserve being a module.

Proposition:

  1. Transitions preserve HM descendancy, MRO, includer-of and kind-of relations in the following weak sense:
  2. Transitions do not preserve the above relations in the strong sense in general - the relations in S are not necessarily restrictions of their counterparts in S'. In particular, for an includer x,
Module inclusion
A single module inclusion is a transition S → S' with two parameters: Transition request is accepted iff the following holds: The structure S' equals S (possibly) except for the inclusion list p.incs' which is defined as follows. Denote A = p.incs & ([q] + q.incs) so that A = [a1, …, an] is the (possibly empty) list of common elements of p.incs and [q] + q.incs, ordered according to p.incs. Then the p.incs' list is defined as the concatenation of lists l0, …, ln where This means that includee chunks si, without any modules that appear along the p.ancs ancestor chain (note that this can only happen if p is a class or eigenclass, for modules p the subtraction is already made by excluding elements from A) are prepended before the start for i == 0 and inserted after ai for 1 ≤ i ≤ n.

In most cases, A is empty, so that

i.e. q's inclusion list, without the modules already appearing in p.ancestors, is prepended to p's inclusion list. Subsequently, the single element list [q] is prepended.
The following example illustrates the above description.
T0, T1, T2, S0, S1, S2, A1, A2 = Array.new(8).map { Module.new }

module P; include T0, A1, T1, A2, T2 end
module Q; include S0, A2, S2, A1, S1 end

module P; include Q                  end

p P.included_modules    #--> [Q, S0, T0, A1, S1, T1, A2, S2, T2]

Inclusion methods
The standard way to perform module inclusion is via the include method of Module, e.g. class X; include M end.

The extend method of Kernel provides a shorthand for inclusion into eigenclasses: x.extend(M) is roughly equivalent to class << x; include M end. Each of include and extend might be considered outer methods of the form

so that there are actually 4 different methods of module inclusion together with 2 hooks:
    eigenclass shift → 0 1
outer/inner ↓
Outer method include extend
Inner method append_features extend_object
Hook included extended
The following code demonstrates the differences.
module M; end
class << M
  def append_features(x); puts "inner: #{self} as includee of #{x}"; super end
  def included(x);        puts "hook:  #{self} as includee of #{x}" end
  def extend_object(x);   puts "inner: #{self} as extender of #{x}"; super end
  def extended(x);        puts "hook:  #{self} as extender of #{x}" end
end
class X; end
x = X.new

class << x; include M end   # inner: M as includee
                            # hook:  M as includee
x.extend(M)                 # inner: M as extender
                            # hook:  M as extender

Notes:

Inclusion anomalies
According to the definition, module inclusion allows anomalies known as double inclusion problem or dynamic inclusion problem []. In particular:
  1. Module inclusion is not necessarily commutative.
  2. The includer-of relation can be non-transitive. i.e. HM descendancy is not transitive in general.
  3. The includer-of relation can have cycles, i.e. HM descendancy is not antisymmetric in general.
  4. MRO ancestor lists can be repetitive (in contrast to sc-inheritance ancestor lists).

Examples:

V1: Value domain

A V1 structure is of the form (O, Φ, ΦA, FALSE, TRUE, NULL, UNDEF, ℤ, ℱ, ℬ, .φvalue) where Objects x for which x.φvalue is defined are called φvalued.

Note: Gray color indicates that merging V1 and S2 structures yields a single meaning for the O symbol.

The following condition is required:

(V1~1) Tuples of atomic elements belong to the value domain, i.e. all finite products ΦA × ΦA ×× ΦA are subsets of Φ.

Note:

S3: Immediate values

An S3 structure is an S2 structure equipped with the V1 structure and with the following structures: The terminals false, true, and nil together with fixnums and symbols are called immediate values.

The structure is subject to the following axioms:

(S3~1) Immediate values are φvalued according to the following table:
Immediate value x x.φvalue
falseFALSE
trueTRUE
nilNULL
Instance of Fixnum Element of
Instance of Symbol Element of ×, see Strings 
(S3~2) The restriction of .φvalue to the set of all immediate values is injective.
(S3~3) The terminals false, true, and nil are the only instances of their respective classes.
(S3~4) Classes of immediate values are quasifinal.

Notes:

Transitions
(S3-T~1) .φvalue is preserved on immediate values.

S4: Actuality

An S4 structure is an S3 structure equipped with (.actual?) where Objects with .actual? set to true are actual(s), otherwise are non-actual(s). We also provide a set-alternative for .actual? by denoting Oa the set of all actual objects.

The structure is subject to the following axioms:

(S4~1) Only finitely many objects can be actual.
(S4~2) Only eigenclasses can be non-actual.
(S4~3) .ce preserves actuals. For every eigenclass x, if x is actual then x.ce is actual.
(S4~4) .sc preserves actuals. For every eigenclass x, if x is actual then x.sc is actual.
(S4~5) If r.ec(i) is actual then so is r.class.ec(i).
(S4~6) r.class.ec, the Class's eigenclass, is actual.
(S4~7) Only actuals can be included or have includees.
(S4~8) Eigenclasses of immediate values are non-actual.
Conventional extent(s)
We say that actuality has We can also speak about minimal extent if Oa == (primary objects) ((first) helix eigenclasses).
Actual lists
For a primary object x, we denote x.actuals the list corresponding to the finite .ec-subchain of actual objects starting at x.

Note that under conventional actuality, x.actuals.length ≤ 2 for every primary object x.

Helix actuals
Axioms (S4~5) and (S4~6) can be equivalently stated as follows.
(S4~5) Helix actual lists are equally sized.
(S4~6) Eigenclasses of helix classes are actual.

Proposition:

The actualclass map
The .aclass function maps objects to actual non-terminals (classes or actual eigenclasses). For every object x, it is defined recursively by
  1. x.aclass == x.ec if x.ec is actual, else
  2. x.aclass == x.ec.sc (== x.class) if x is terminal, else
  3. x.aclass == x.sc.aclass.
We call x.aclass the actualclass of x.

Proposition:

  1. For every actual object x,
    1. x.aclass equals the first actual member of x.ec.hancs,
    2. (x.class ==) x.ec.hancestors[0] == x.aclass.hancestors[0].
  2. The .aclass map forms an algebraic tree – the actualclass tree.
  3. The root of the actualclass tree equals r.class.actuals.last.
  4. The depth of the tree equals r.actuals.length + 1 (3 under conventional actuality).

Example: Assume conventional actuality and let A be a direct subclass of Object and a a direct instance of A. Then there are four possible .aclass chains from a to Class.ec according to whether a.ec and A.ec are actual or not.

a A Object.ec Class.ec
a A A.ec Class.ec
a a.ec A.ec Class.ec
a a.ec Object.ec Class.ec
The .ec ≤ .aclass ≤ .class refinement
The proposition from the previous subsection shows that the actualclass map, .aclass, can be considered a refinement of the class map, .class. Further observation shows that .aclass can be considered a coarsement of the eigenclass map, .ec, so that we have the following refinement chain:
.ec ≤ .aclass ≤ .class.
This can be stated precisely as follows. (Recall that the HM descendancy restricted to non-terminals equals the inheritance which is an algebraic tree.)

Proposition:

  1. For every object x,   x.ec ≤ x.aclass ≤ x.class.
    (The maps .ec, .aclass and .class form a chain when ordered pointwise by the HM descendancy.)
  2. For every object x,
    x.ec is the first member of x.ec.hancs,
    x.aclass  is the first member of x.ec.hancs that is actual,
    x.class is the first member of x.ec.hancs that is a class.

    Note: Gray color indicates that statements are valid for both .hancs and .ancs.

  3. Any .f of the .ec, .aclass or .class maps is monotone with respect to the inheritance , i.e. for every non-terminals x, y,   x ≤ y implies x.f ≤ y.f.
  4. For every object x and every i > 0,   x.ec(i) ≤ x.aclass(i) ≤ x.class(i).
Semiactuals
In order to describe how the .aclass map is related to MRI 1.9 implementation, we introduce a function .saec, meaning semiactual eigenclass, partially defined for primary objects as follows:
  1. x.saec is defined iff x is
    1. a class or
    2. a terminal such that x.actuals.length > 2.
  2. If x.saec is defined then it equals x.actuals.last.ec.
Eigenclasses from the range of .saec are called semiactual(s). Thus, semiactuals are (right) covers of actual lists except for 1-2 sized actual lists starting with terminals – such lists are uncovered.

We also introduce a 3-valued attribute of .actuality which is defined on all objects according to the table below.

Object set x.actuality
non-actuals 0
  semiactuals 1
actuals 2
An extra condition is imposed by MRI:
(S4X~1) For every terminative eigenclass x, if x is actual or semiactual then x.sc.ec is actual or semiactual.
The .klass map
The .klass map provides a virtual connection to object's eigenclass. We consider two versions:
  1. (A) Restricted version: .klass is a partial map between actual objects. For every actual object x,
    1. (1) x.klass == x.ec if x.ec is actual, else
    2. (2) x.klass == x.ec.sc (== x.class) if x is terminal, else
    3. (3) x.klass is undefined.
  2. (B) Full version: .klass is a map between actual or semiactual objects defined according to the MRI 1.9 implementation. For every actual or semiactual object x,
    1. (1) x.klass == x.ec if x.ec is actual or semiactual, else
    2. (2) x.klass == x.ec.sc (== x.class) if x is terminal, else
    3. (3) x.klass == x.sc.ec if x.pr is terminal and x is actual or semiactual, else
    4. (4) x.klass == c.ec(x.eci) (if x.pr is a class and x semiactual – the value is probably unimportant).

Proposition:

  1. In its restricted version, the .klass map is a restriction of both the .aclass and the full version of .klass.
  2. The restricted version of .klass is a generator of .aclass in the following sense: For every actual x,
Transitions
(S4-T~1) State transitions S → S' are incremental in the S4 structure in the following sense. For every object x from OO',
  1. if x is actual in S then it is so in S'
Eigenclass actualization
An eigenclass actualization is a transition S → S' with a single parameter The structure S' equals S (possibly) except for the set of actual objects. The difference between sets of actual objects, denoted x.acdelta, is defined as follows (all functions are taken in S).
  1. (A) With (S4X~1) imposed:
    1. (1) x.acdelta == [] if x.ec is actual, else
    2. (2) x.acdelta == [x.ec] if x is terminal, else
    3. (3) x.acdelta == [x.ec] + x.sc.acdelta if x.pr is a non-helix class, else
    4. (4) x.acdelta == r.class.hancs.map{|c| c.ec(x.eci+1)} if x.pr is a helix class, else
    5. (5) x.acdelta == [x.ec] + x.sc.ec.acdelta if (x.pr is terminal and) x.eci != 1, else
    6. (6) x.acdelta == [x.ec] + x.sc.acdelta + x.sc.ec.acdelta (if x is the eigenclass of a terminal).

    Note: The recursive definition in (5) and (6) requires that x.acdelta is defined also for non-actuals.

  2. (B) Without (S4X~1):
    1. (1)–(4) as in (A).
    2. (5) x.acdelta == [x.ec] + x.sc.acdelta (if x.pr is terminal) – the same prescription as in (3) applies.

Proposition: In case (B), x.acdelta, as a set, equals the union of the following (possibly empty) sets Δ1 and Δ2 (all functions are taken in S):

  1. Δ1 equals x.ec.hancs - x.aclass.hancs.
  2. Δ2 equals
    1. r.class.actuals.last.ec.hancs - y.hancs if y is the .sc-least helix object of Δ1, or
    2. empty set, if Δ1 does not contain a helix object.
Informally, we first actualize eigenclasses up to x.aclass and then equalize helix actual lists.

The following examples of Ruby code show several ways of x's eigenclass actualization.

  1. (A) Opening or explicitly referencing x.ec.
    1. (1) class << x; end
    2. (2) x.singleton_class
  2. (B) Extending x.ec's inclusion list (or attempting extension).
    1. (1) x.extend(Module.new)
    2. (2) x.extend(Kernel) (including an already included module)
  3. (C) Defining x.ec's own method (aka singleton method of x)
    1. (1) def x.dummy; end
    2. (2) x.instance_eval { def dummy; end }
    3. (3) x.define_singleton_method("", lambda{})
    4. (4) x.module_function(:m) (if x is a module having own method m)

Notes:

The built-in T_CLASS counter
As of MRI 1.9, ObjectSpace.count_objects[:T_CLASS] counts the following objects in total:
  1. classes,
  2. actual eigenclasses, and
  3. semiactual eigenclasses.
It means in particular, that defining a class increases the counter by 2, one for the class and one for its semiactual eigenclass:
module Kernel
  def nnt_delta     # number of non-terminals delta
    @nnt ||= 0
    nnt = ObjectSpace.count_objects[:T_CLASS]
    delta = nnt - @nnt
    @nnt = nnt
    delta
  end
  def nnt_delta_report; puts "nnt_delta: #{nnt_delta}" end
end

nnt_delta_report  # nnt_delta: 387
class X; end
nnt_delta_report  # nnt_delta: 2

Proposition: Assuming (S4X~1), for every actual x which is not an immediate value, x.acdelta.length equals

  1. nnt_delta - 1 if x.eci == 1, x.pr is terminal and x.pr.actuals.length == 2,
  2. nnt_delta otherwise.

S5: Includer containment

An S5 structure is an S4 structure equipped with (.cparent, .cname) where We introduce the following additional notation / terminology. An S5 structure is subject to the following axioms:
(S5~1) The containment Ϲ is an algebraic forest (with .cparent undefined on roots).
(S5~2) There is exactly one containment root x such that x.cname is defined, namely the Object class.
(S5~3) If x.cparent is defined then x.cname is defined. (Non-roots are named.)
(S5~4) For every eigenclass x, x.cname is undefined. (Eigenclasses are anonymous, therefore roots.)
(S5~5) Anonymous classes and modules have no containment descendants. (Thus, being roots, they are containment singletons.)
(S5~6) Non-actuals have no containment descendants. (Thus, being roots, they are containment singletons.)
(S5~7) Containment names are constant names. (This statement comes into effect as soon as the meaning of being a constant name is defined.)
(S5~8) If x.cparent is Object then x.cname differs from Object.cname.
Components

Proposition: Components of the containment forest are trees of the following 3 types:

  1. (A) The unique main tree rooted at Object.
  2. (B) Single-element trees of anonymous classes or modules.
  3. (C) Trees rooted at eigenclasses (usually also of cardinality 1).

Note:

Containment ancestors
Similarly to inheritance ancestor lists, we denote x.cancs the list of containment ancestors of x, i.e.
  1. x.cancs is defined iff x is an includer,
  2. x.cancs[i] == y iff x.cparent(i) == y.
We also denote
  1. x.croot the containment root of x,   x.croot == x.cancs.last,
  2. x.cpathname the (proper) containment path-name of x defined by
    x.cpathname == (x.cancs - [x.croot]).reverse.map{|y| y.cname}.join("::"),
    i.e. non-root ancestor names are reversed and joined by "::". Note that for every containment root x (in particular, Object), x.cpathname is an empty string.
Transitions
The following rule applies:
(S5-T~1) State transitions S → S' preserve x.cparent and x.cname whenever x.cparent is defined.
A single containment binding is a transition S → S' with three parameters: The following table shows examples of Ruby code with requests for containment binding.
Group Container p Name n Ruby code Containee q (after transition)
q.croot q.cpathname
A Object "Q" class Q; end Object Q
X class X; class Q; end end Object X::Q
X X::Q = Class.new Object X::Q
C1 X.ec class << X; class Q; end end X.ec Q
q = Class.new
C2 X.ec   X.singleton_class::Q = q
unchanged
B x x::Q = q
x x.class_eval { self::Q = q }

Object relation summary

Notation / Expression Terminology / Description Domain Generating map Relation characteristics
== (Classes, ≤) inheritance non-terminals .sc algebraic tree
primorder all objects .ec component-wise isomorphic to the linear order of natural numbers
(classes) self-or-subclass-of classes .sc ↾ (classes) finite algebraic tree
Μ self-or-own-includer-of includers reflexive and antisymmetric
  == (Μ) ∪ Μ HM descendancy includers reflexive
(Μ, ≤) MRO Μ .super algebraic forest
instance tree all objects .class  (aka direct-instance-of) algebraic tree of depth 2
.class ◦ instance-of all objects complete (any-to-any) on helix classes, irreflexive and antisymmetric otherwise
.ec ◦ ≤ kind-of all objects
actualclass tree all objects .aclass algebraic tree (of depth 3 under conventional actuality)
Ϲ includer containment includers .cparent algebraic forest

S6: Frozenness, taintedness and trust

An S6 structure is an S5 structure equipped with (.frozen?, .tainted?, .trusted?) where According to these attributes, objects can be frozen/non-frozen, tainted/untainted and trusted/untrusted. The structure is subject to the following axioms:
(S6~1) If x is frozen then x.ec is frozen.
(S6~2) If x is tainted then x.pr is tainted. (.ec-chains are tainted as a whole.)
(S6~3) If x is trusted then x.pr is trusted. (.ec-chains are trusted as a whole.)
(S6~4) Immediate values are trusted.
Transitions
(S6-T~1) Transitions S → S' preserve being frozen, i.e. if x is frozen in S then it is so in S'.
(S6-T~2) Inclusion lists of frozen includers cannot be extended.
(S6-T~3) Transitions preserve taintedness of frozen objects.
(S6-T~4) Transitions preserve trust of frozen objects.
(S6-T~5) Transitions preserve .φvalue of frozen objects.
Transitions affecting frozennes and taintedness are accomplished via the freeze and taint/untaint methods, respectively.

S7: Object cursors

An S7 structure is an S6 structure equipped with (nestlists, selfs) where Additional notation/terminology is introduced: The structure is subject to the following axioms:
(S7~1) Classes and modules that appear in nesting are named.
(S7~2) main is a direct instance of Object.
Containment pseudorule
The following condition often happens to be satisfied: We call this condition a pseudorule because it only reflects a common pattern of a nested class/module definition, e.g.
class A
  class B
    class C
      # current nesting corresponds to A::B::C
    end
  end
end
Writing class ::C instead of class C would break the pseudorule.
Transitions
(S7-T~1) The main context main remains unchanged across transitions.
Includer opening/closing
There are two (basic) ways how nesting gets changed: explicit and implicit. An explicit change is performed via class/module/eigenclass (re)definition. In this case, nesting changes correspond to prepending/removing one object to/from the front of nesting, i.e. they are equivalent to nesting.unshift(y) and nesting.shift, respectively. The unshift operation is includer opening and can have one of the following forms: The shift operation is includer closing and is accomplished by end.
Evoked nesting
An implicit nesting change is obtained via method invocation as demonstrated by the following code:
class X
  class Y; def self.nest1; Module.nesting end end  # def-nesting is [X::Y, X]
  def   Y          .nest2; Module.nesting end      # def-nesting is [X]
end
class A
  p Module.nesting    # [A]
  p X::Y.nest1        # [X::Y, X]
  p X::Y.nest2        # [X]
end
The example shows that each method has its own nesting which becomes the current nesting after method invocation. The method's nesting equals the current nesting at the time of method definition.

S8: Object data-representatives

An S8 structure is an S7 structure equipped with (Ω, ω(), OID, .oid, IVN, .invals) where The following conditions are required:
(S8~1) ω(a,b) is defined iff the following are satisfied:
  1. Both a and b are actual.
  2. If a is an includer then (a,b) belongs to the MRO domain.
  3. If a is a non-includer (pure instance) then it equals b.
Iclasses
We call elements ω(a,b) with a different from b iclasses (inclusion-classes).

Condition (S8~1) says that Ω is a copy of all actual MRO domain pairs extended by all pairs (x,x) with x being a pure instance (non-includer). Equivalently, Ω is a copy of the set of all actual objects extended by the set of iclasses.

This is illustrated by the following diagram.

Extended actual O Data representatives Extended actual Μ
 
iclasses
actual MRO pairs  
actual objects
actual includer representatives
non-includer representatives
Induced maps
We define additional (partial) functions on Ω:
Data fields
Because ω-objects are identifiable with OIDs we can regard functions defined on Ω as data fields. The following table presents a distinguished data-field subset.
Field
Name
Domain
(Type)
Applicability Description Relevant field(s) in
Ruby implementation
non-includer includer iclass
oid OID ω-object id
super OID MRO parent super
ce OID eigenclass predecessor __attached__
klass OID actualclass generator klass
module OID iclass originator
cparent OID containment parent __classid__ and/or
__classpath__
cname string containment name
frozen? boolean frozenness part of flags
tainted? boolean taintedness
trusted? boolean trust
type ENUM_T basic type
invals IVN Φ internal value(s)

Notes:

  1. Underline in oid indicates a primary key.
  2. There is no equivalent of cparent in Ruby's implementation.
  3. The quotation marks in string indicate that the domain of of cnames should be more precisely described as φvalues of Symbols.

The type field indicates the basic type with the following enumeration domain (we assume that ENUM_T is a subset of Φ):

Value Meaning of ω(x,y)
T_ICLASS iclass
T_CLASS class or eigenclass
T_MODULE module
T_NONINC non-includer (pure instance)
The ωobjects data table
Using the fields from the previous subsection we obtain an ωobjects data table, with one-to-one correspondence between rows and object data-representatives.
oid super ce klass module cparent cname frozen? tainted? trusted? type invals
              
               
               

Proposition: The ωobjects data table uniquely determines the S6 structure, up to isomorphism and except for the .φvalue function.

Class nomenclature summary
Prefix Notation Terminology Description
class A primary non-terminal object.
e eigenclass A secondary object.
metaclass A (non-strict) inheritance descendant of the Class class.
explicit metaclass A metaclass that is a class. In Ruby, the Class class is the only explicit metaclass. The set of explicit metaclasses equals the set of classes of classes.
implicit metaclass A metaclasss that is an eigenclass. The set of implicit metaclasses equals the set of eigenclasses of Classes.
e .ec the eigenclass map A map from objects to eigenclasses.
s .sc the superclass map A partial map between non-terminal objects.
.class the class map A map from objects to classes. The second application, .class(2), is constant.
direct-subclass-of A relation which equals the restriction of .sc to classes.
subclass-of The transitive closure of direct-subclass-of.
direct-superclass-of, superclass-of Inverses of direct-subclass-of and subclass-of, respectively. Not used in this document to avoid terminological conflicts with the superclass map.
direct-instance-of Equivalent to .class (i.e. x direct-instance-of y iff x.class == y).
instance-of Composition of direct-instance-of and self-or-subclass-of.
class-of Inverse of direct-instance-of.
Class (A) the Class class The instance tree root, and, simultaneously, the metaclass tree root. Denoted c, equal to r.class.
(B) a Class A class or an eigenclass. An instance of the Class class. An object that is kind-of Class.
Classes Class instances Classes and eigenclasses.
helix classes Classes that belong to the Ruby helix. Members of c.hancs, i.e. Class, Module, Object, and BasicObject.
a .aclass the actualclass map A map from objects to actual eigenclasses or classes. The n-th application, .aclass(n), is constant, where n equals r.actuals.length + 1 (n == 3 under conventional actuality).
i iclass An inclusion class, abstraction of an includer-includee pair.
.klass the actualclass generator
  • (A) A partial map between actual objects.
  • (B) A map between actual or semiactual objects.
  • (C) An extension of (B) to iclasses.
singleton class Equivalent to eigenclass. Not used in this document to avoid a conflict with the term class.

Note: The word singleton refers to injectivity of .ec.

 

D0: Estrings

A D0 structure is an S8 structure equipped with (Encoding, ℇ, ℇ¢, ℇ$, .name, e8b, e7b, ⅀, ⅀, ≘) where Note that we already have axiomatized the inclusion chain {e8b, e7b} $ ¢ .

The structure is subject to the following axioms:

(D0~1) ('',e) is a valid estring for every encoding e.
(D0~2) (s,e8b) is a valid estring for every bytestring s.
(D0~3) (s,e7b) is a valid estring iff s is an ascii bytestring.
(D0~4) (s,e7b) ≘ (s,e8b) whenever (s,e7b) is a valid estring.
(D0~5) (s,e) ≘ (t,e) implies s == t, i.e. valid -equivalent estrings with the same encoding are equal.
(D0~6) For every ascii-compatible encoding e and every valid estring (s,e7b),
  • (s,e7b) ≘ (t,e)
for some (necessarily unique) estring (t,e).
(D0~7) For every encoding e from ¢ and every bytestrings s, t such that (s,e) is valid,
  • (s + t, e) is valid iff (t,e) is valid.
(D0~8) If a, b are leading characters of x, y, then
  • x ≘ y implies a ≘ b.

() For a valid estring x = (s,e) with non-empty bytestring s and encoding e from ¢, we denote x.chr the leading character of x defined as the unique valid atomic prefix of x, i.e. it is a valid estring (u,e) such that

The following table shows Ruby built-in boolean-attribute correspondents for encoding or estring set-membership.
Set membership Ruby boolean-attribute reflection
Expression Terminology / description
x ¢ encoding x supports proper character handling !x.dummy?
x $ encoding x is ascii-compatible x.ascii_compatible?
x estring x is valid x.valid_encoding?
estring x is ascii-only x.ascii_only?
We also denote
The .encode() map
The .encode() function maps valid estrings to their -equivalents in given encodings, i.e. it is a partial function from × to such that

Proposition:

Ascii and ascii-only estrings
Valid estrings x are called For an ascii estring x and an ascii bytestring s, we might write x == s for x == (s,e7b). (This is later applied, for instance, for x == 'method_missing'.)
Strict concatenation and character decomposition
The strict concatenation is a partial binary operator on the set ¢ of char-decomposable estrings defined by The character decomposition (split) .chars is a function from ¢ to finite lists over ¢ defined recursively by For an estring s from ¢, An estring character is an estring of length 1.

Proposition:

  1. The structure (⅀e, ∔, ('',e)) is a free monoid for every encoding e from ¢.
  2. s == s[0] ∔ s[1] ∔ ⋯ ∔ s[n-1] for every char-decomposable estring s of length n.
  3. For every encodings e, f from ¢, In particular,
Loose concatenation
We partially specify loose concatenation as a partial binary operator + on valid estrings satisfying the following: In particular an estring x can start with an ascii estring even if x is not ascii.
Transitions
(D0-T~1) For transitions S S', all of the following are fixed for encodings and estrings existing both in S and S': the ¢- and $- set memberships, validity of estrings, the -equivalence.
(D0-T~2) Encoding names are preserved across transitions.

D1: Strings

A D1 structure is a D0 structure equipped with (String, .estr) where The structure is subject to the following axioms:
(D1~1) Strings and Symbols are φvalued by pairs of bytestrings as follows:
  • x.φvalue == (s, e.name)
where (s,e) == x.estr.
(D1~2) For every symbol x, the estring x.estr is valid.
(D1~3) For every symbol x, if x.estr ≘ (s,e7b) for some s then x.estr == (s,e7b).
(I.e. ascii-only symbols are ascii.)

Notes:

Transitions

Proposition: The following are consequences of the already introduced conditions:

  1. .estr is preserved on symbols.
  2. .estr is preserved on frozen strings.

D2: Arrays

A D2 structure is a D1 structure equipped with (Array, ._list) where Arrays are called arrays.

Notes:

  1. Recall that a finite list is a function with the domain being an index set of the form 0, …, n-1 for some natural n.
  2. By the introduced terminology, arrays can be considered as maps to lists, but not lists themselves. For instance, there is only one empty list, but possibly many non-identical empty arrays.
Let a be an array and i an integer. Then
Transitions
(D2-T~1) Transitions S → S' preserve ._list on frozen arrays, i.e. if a is a frozen array, then a[i] equals a[i]' and a.length equals a.length'.

D3: Hashes

A D3 structure is a D2 structure equipped with (Hash, .hcodes, .keys, .values, .compare_by_identity?, .dflt, .dflt_call?, Proc) where Hashes are called hashes. The remaining members are functions on the set of all hashes. For every hash x, Every hash x is subject to the following conditions:
(D3~1) The list x.hcodes.zip(x.keys) (x.hcodes paired with x.keys) is non-repetitive.
(D3~2) If x.compare_by_identity? is true then even the list x.keys is non-repetitive.
(D3~3) If x.dflt_call? is true then x.dflt is an instance of Proc.
(D3~4) If s is a direct instance of String contained in x.keys and x.compare_by_identity? is false then s is frozen.

For a hash x, the triple (x.hcodes, x.keys, x.values) has the following equivalent forms:
  1. (A) The single-list form.
    The hash is a list of triples of the form (hash-code, key, value). The triples have unique (hash-code, key) pairs. If x.compare_by_identity? is true then even keys are unique.
  2. (B) The slot form.
    The hash is a map from hash-codes (slots) to lists of triples of the form (idx, key, value) such that

Example: The following diagrams show a hash x in the two above described forms. Note that due to multiple occurrence of the 'a' key, x.compare_by_identity? is necessarily false.

idx hcode key value
0m 
1a 
2r 
3c 
4a 
5b 
6q 
7d 
8u 
9a 
10b 
11g 
     
3c   1a   0m   5b 
8u   2r   4a   6q 
9a   7d   11g 
10b    
Transitions
(D3-T~1) Transitions S → S' preserve the 6 member maps .hcodes, …, .dflt_call? on frozen hashes.
(D3-T~2) True values of .compare_by_identity? are preserved. (Once being set to true, this boolean attribute cannot be set to false.)
Hash resolution
By an immutable hash resolution context we mean a structure (.hash, .eql?(), .call()) where For a hash x and an object k we say that i is a resolution key-index of k in x if either of the following is satisfied:
  1. (A) x.compare_by_identity? is false and i is the smallest index such that
    1. k.hash == x.hcodes[i] and either k == x.keys[i] or k.eql?(x.keys[i]),
    or
  2. (B) x.compare_by_identity? is true and i is the unique index such that
    1. k == x.keys[i].
The (hash) resolution operator [] is an object-valued function on Hashes × O assigning each hash x and each object k a value x[k] as follows:
  1. (1) x[k] == x.values[i] if i is the resolution key-index of k in x, else, if there is no such i,
  2. (2) x[k] == x.dflt if x.dflt_call? is false, else
  3. (3) x[k] == x.dflt.call(x,k).

D4: Numbers

A D4 structure is a D3 structure equipped with (Bignum, Float, Rational, Complex, .real, .imag) where

Note: The Fixnum < Integer < Numeric class chain has already been introduced, see Immediate values.

The structure is subject to the following axioms:

(D4~1) Integers are either Fixnums or Bignums.
(D4~2) Bignum, Float, Rational and Complex are quasifinal (in addition to Fixnum).
(D4~3) Integers are φvalued by the integers .
(D4~4) Bignums and Fixnums have disjoint .φvalue images.
(D4~5) Floats are φvalued by the floating point numbers .
(D4~6) Rationals are φvalued by pairs (n,d) from × such that
  • d is positive, and
  • n and d are relatively prime.
Transitions
(D4-T~1) The .real and .imag maps are preserved on all Complexes.

D5: Ranges

A D5 structure is a D4 structure equipped with (Range, .start, .end, .exclude_end?) where
Transitions
(D5-T~1) The .start, .end and .exclude_end? maps are preserved on all Ranges.

Object internal value data

We specify the invals data (sub)fields in the ωobjects data table as follows.
Applied to Field
Name
Domain
(Type)
Description
false value {FALSE}
true value {TRUE}
nil value {NULL}
Encodings name encoding name
dummy? boolean indicates stateful encoding without proper character handling
ascii_compatible? boolean indicates ascii-compatible encoding
Symbols and Strings bytes byte-sequence together with encoding
encoding ENUM
valid_encoding? boolean indicates whether bytes is a valid sequence w.r.t encoding
Arrays length array length
Hashes length hash length
compare_by_identity? boolean indicates hash resolution mode
dflt OID the hash's default object or evaluator
dflt_call? boolean indicates dflt interpretation mode
Fixnums value the φvalue of a fixnum
Bignums value the φvalue of a bignum
Floats value the φvalue of a float
Rationals numerator the first component of the φvalue
denominator the second component of the φvalue, needs to be positive
Complexes real OID the real-part object
imag OID the imaginary-part object
Ranges start OID range start
end OID range end
exclude_end? boolean range end exclusion indicator

Notes:

  1. Gray color of a field name indicates that field value is derived.
  2. Array or hash lists are not considered to be part of invals data.

 

A0: Lexical identifiers

An A0 structure is a D5 structure equipped with (.idcat) where Elements from the domain of .idcat are called (lexical) identifiers, x.idcat is x's identifier category.
(A0~1) The .idcat partial function categorizes estrings according to the following table:
Estring s s.idcat Partition
s starts with an uppercase letter 'constant-identifier' β
s starts with "@" but not with "@@" 'instance-variable-identifier'
s starts with "@@" 'class-variable-identifier'
s starts with "$" 'global-variable-identifier'
s starts with a lowercase letter or with _ 'local-variable-identifier'
α
s starts with a letter or with _ 'method-identifier'
(A0~2) Identifiers are subject to additional restrictions which are not specified in this document.
The last column in the definition table indicates two partitions of strings induced by .idcat.

We will also use the word name for non-method identifiers and apply the categorization directly to symbols, so that e.g. for a symbol s, s.estr.idcat == 'constant-identifier' means s is a constant name.

A1: Methods

An A1 structure is an A0 structure equipped with (Π, .met(), μ) where Thus, .met() can be viewed as a subset of O × Υ × Π. If x.met(s) is defined then we say that x has own method s or that x is a method-owner of s. If, in addition,

An A1 structure has the following axioms:

(A1~1) Only actual objects can have own methods.
(A1~2) Only includers can have own methods.

If in initial state, an A1 structure satisfies the following:

(A1~3) The inheritance root r is an nwo-method owner of μ (the method_missing method).

Notes:

  1. No restriction applies to a symbol s to become a method name. In particular, if x.met(s) is defined then it is not required that s.estr.idcat == 'method-identifier', see notes to Transitions.
  2. As of MRI 1.9, the following condition is also satisfied:
    (A1~4*) Eigenclasses x.ec of Numerics x cannot have own methods.
Method owner
We define a partial function .mowner() from O × Υ × {true, false} to O, called method owner map, by
x.mowner(s, wo) == y iff
  • (1) x is an includer,
  • (2) y equals the least-indexed member of x.ancs that is a method owner of s,
  • (3) if wo is false then y is also an nwo-method owner of s.
We allow false to be the default value of wo, so that x.mowner(s) means x.mowner(s, false).

Propositions:

  1. x.mowner(s) == x  iff  x is an nwo-method-owner of s.
  2. x.mowner(s,wo).mowner(s,wo) == x.mowner(s,wo) whenever x.mowner(s,wo) is defined.
Method inheritor
We say that x is a method-inheritor of s if x.mowner(s, true) is defined, i.e. if some of x.ancs has own method s. We say that x is an nwo-method-inheritor of s if x.mowner(s) is defined.

By an inherited method map we mean the partial function .met_h() from O × Υ × {true, false} to Π defined by

x.met_h(s,wo) == x.mowner(s,wo).met(s)
whenever x.mowner(s,wo) is defined. Again, the default value for wo is false.
Simplified method resolution
By a simplified method resolution map we mean a (partial) function smr() from O × Υ to O × Υ defined as follows:
  1. (1) smr(x,s) == (x.ec.mowner(s), s) if x.ec.mowner(s) is defined, else
  2. (2) smr(x,s) == (x.ec.mowner(μ), μ) if x.ec.mowner(μ) is defined, else
  3. (3) smr(x,s) is undefined.

Notes:

Interchangeability of .ec and .aclass

Proposition: For every object x and every symbol s,

This means that the method resolution for a receiver x can actually start from the actualclass of x.
Transitions
(A1-T~1) Transitions S → S' preserve .met() on frozen objects, i.e. if x is a frozen includer, then x.met(s) equals x.met'(s).
This means that the own method map .map() can be arbitrarily (re)defined on non-frozen objects. Modifications of .map() are accomplished via transitions S → S' of types (A)–(E) according to the following table.
Transition parameters Requested output condition Ruby's correspondent(s)
(A) New method definition
(1)
  1. x, the requested owner,
  2. s, the requested method name,
  3. m, the requested method (body).
x.met'(s) == m
  1. (a) def s; m end,
  2. (b) define_method(s,m).
(2)
  1. x, the object with x.ec the requested owner,
  2. s, m as in (1).
x.ec.met'(s) == m
  1. (a) def x.s; m end,
  2. (b) x.define_singleton_method(s,m).
(B) Aliasing an inherited method
  1. x, the requested owner,
  2. a, the requested alias name,
  3. s, the name of the method that is requested to be aliased.
x.met'(a) == x.met_h(s)
  1. (a) alias a, s,
  2. (b) alias_method(a,s).
(C) Whiteouting an inherited method
  1. x, the requested owner,
  2. s, the name of the method that is requested to be whiteouted.
x.met'(s) == π
  1. (a) undef s,
  2. (b) undef_method(s).
(D) Removing an own non-whiteout method
  1. x, the requested owner,
  2. s, the name of the method that is requested to be removed.
x.met'(s) is undefined
  1. (a) —,
  2. (b) remove_method(s).
(E) Changing method visibility – see Method visibility transitions

Notes:

  1. If x is not specified in the rightmost column, then it is assumed that x equals self unless self == main – in this case x == Object.
  2. As of Ruby 1.9, direct transitions for removing a whiteout method are not supported.
  3. In (a) cases, the restriction s.estr.idcat == 'method-identifier' applies to the method name s (or to the alias a). In (b) cases, no such restriction applies, as shown in the following example.
    class X
      define_method ("")      { self }
      define_method ("1 + 1") { 3 }
      alias_method  "@a", ""
    end
    p X.new.send("").send("@a").send("1 + 1")  #-> 3
    p X.instance_methods(false)                #-> [:"", :"1 + 1", :@a]
    class X
      undef_method  "1 + 1"
      remove_method "", "@a"
    end
    p X.instance_methods (false)               #-> []
    

A2: Properties

An A2 structure is an A1 structure equipped with (.pty(), κ) where Thus, .pty() can be viewed as a subset of O × Υ × O. We call elements of the set Υ × O properties. We say that an object x has own property (s,y) if x.pty(s) == y. We might just say that x has own property s.

We categorize properties according to .idcat:

Symbol s Property (s,y) category
s.estr starts with an uppercase letter constant
s.estr starts with "@" but not with "@@" instance variable
s.estr starts with "@@" class variable
s.estr starts with "$" global variable
s.estr starts with a lowercase letter or with _ local variable

Notes:

An A2 structure has the following axioms:

(A2~1) Only actual objects can have own properties.
(A2~2) Only includers can have own constants.
(A2~3) Only includers can have own class variables.
(A2~4) Only the Kernel module can have global variables.

Note: Global variable ownership is a rather artificial concept. We have chosen the Kernel as the owner, because it owns the global_variables method for global variable enumeration.

If in initial state, an A2 structure satisfies the following:

(A2~5) The metamodule root m is an nwo-method owner of κ (the const_missing method).
Class variables
Roughly speaking, class variables get synchronized across includers comparable either in HM descendancy or in primorder (i.e. belonging to the same eigenclass chain). More specifically, some weak form of the following condition applies:
(A2~6*) For every includers x, y, and every class variable symbol s such that both x.pty(s) and y.pty(s) are defined, each of the following conditions in its own right is sufficient to imply x.pty(s) == y.pty(s):
  1. x ≤ y or y ≤ x.
  2. x.pr == y.pr.
How exactly this weak form looks like is not specified in this document. Neither are specified transition rules or resolution rules which seem to be even more complicated.

Note: Class variables are considered a controversial Ruby feature.

Constant owner
Analogous to method owner map we introduce the constant owner map as a partial function .cowner() from O × Υ to O defined by
x.cowner(s) == y  iff 
  • (1) x is an includer,
  • (2) y equals the least-indexed member of x.ancs that has own constant s.

Propositions:

  1. x.cowner(s) == x  iff  x is a constant-owner of s.
  2. x.cowner(s).cowner(s) == x.cowner(s) whenever x.cowner(s) is defined.
Constant inheritor
We say that x is a constant-inheritor of s if x.cowner(s) is defined, i.e. if some of x.ancs has own constant s.

By an inherited constant map we mean the partial function .cst_h() from O × Υ to O defined by

x.cst_h(s) == x.cowner(s).pty(s)
whenever x is a constant-inheritor of s.
Property owner and inheritor
For the sake of uniformity, we also (partially) define .powner(), the property owner map, and .pty_h(), the inherited property map, as extensions of .cowner() and .cst_h(), respectively.

Notes:

  1. .pty_h() is only related to inheritance in cases (1) and (3).
  2. We assume that .pty_h() factors through .powner() even for class variables.
Qualified constant resolution
By a qualified constant resolution map we mean a partial function qcr() from O × Υ to O × Υ defined as follows:
  1. (0) qcr(x,s) is undefined if x is not an includer or s is not a constant name, else
  2. (1) qcr(x,s) == (x.cowner(s), s) if x.cowner(s) is defined, else
  3. (2) qcr(x,s) == (x.ec.mowner(κ), κ) if x.ec.mowner(κ) is defined, else
  4. (3) qcr(x,s) is undefined.
If qcr(x,s) == (y,t) then we say that (y,t) is a qualified constant resolution of (x,s).

Notes:


The qcr map can be expressed as a method of Module as follows:

class String
  def constant_name?; !!match(/^[A-Z]\w*$/) end
end
class Module
  def cowner(s)
    ancs.find{|x| x.const_defined?(s, false)}
  end
  def qcr(s)
    !s.to_s.constant_name?                ? nil :
    (o = cowner(s))                       ? [o, s.to_sym] :
    respond_to?(t = :const_missing, true) ? [method(t).owner, t] : nil
  end
end
Unqualified constant resolution
By an unqualified constant resolution map we mean a partial function uqcr() from Υ to O × Υ. The start point x is obtained by The lookup sequence q is defined by The uqcr map is then defined as follows:
  1. (0) uqcr(s) is undefined if s is not a constant name, else
  2. (1) uqcr(s) == (w, s) if w is the least-indexed member of q such that w.pty(s) is defined, else, if no such w exists,
  3. (2) uqcr(s) == (x.ec.mowner(κ), κ) if x.ec.mowner(κ) is defined, else
  4. (3) uqcr(s) is undefined.
If uqcr(s) == (y,t) then we say that (y,t) is an unqualified constant resolution of s.

Notes:


The uqcr map can be expressed as a method of Array as follows:

$main = self
class Array
  def uqcr(s)
    return nil if !s.to_s.constant_name?
    x = first || $main.ec
    q = self + x.ancs + (::Class === x ? [] : ::Object.ancs)
    o = q.find{|y| y.const_defined?(s, false)}
    o                                       ? [o, s.to_sym] :
    x.respond_to?(t = :const_missing, true) ? [x.method(t).owner, t] : nil
  end
end

Note: The method must be called with the current nesting as the receiver, i.e. Module.nesting.uqcr(s).

Immediate values revisited
Even immediate values can have state, in particular, they can Statefulness of Fixnums is demonstrated in the following code.
class Fixnum
  def false(i = 1)
    @n_falsified ||= 0
    @n_falsified += i
    self
  end
  def report
    "#{self} falsified #{@n_falsified || 0} times"
  end
end
puts 5.report        # 5 falsified 0 times
puts 6.report        # 6 falsified 0 times
puts 5.false.report  # 5 falsified 1 times
puts 5.false.report  # 5 falsified 2 times
puts 6.false.report  # 6 falsified 1 times
Transitions
(A2-T~1) Transitions S → S' preserve .pty(s) on frozen objects, except for global variables s, i.e. if x is a frozen object and s is a symbol that is not a global variable name, then x.pty(s) equals x.pty'(s).
This means that the own property map .pty() can be arbitrarily (re)defined on non-frozen objects. Modifications of .pty() are accomplished via transitions S → S' according to the following table:
Transition parameters Requested output condition Ruby's correspondent(s)
(A) Property assignment
  1. x, the requested owner,
  2. s, the requested property name,
  3. y, the requested property value.
x.pty'(s) == y
  1. <s> = y,
  2. x.instance_variable_set(s,y),
  3. x.class_variable_set(s,y),
  4. x.const_set(s,y).
(B) Property removal
  1. x, the requested owner,
  2. s, the name of the property that is requested to be removed.
x.pty'(s) is undefined
  1. x.remove_instance_variable(s),
  2. x.remove_class_variable(s),
  3. remove_const(s).

Notes:

  1. If x is not specified in the rightmost column, then
    1. x equals the artificial owner Kernel if s is a global variable name, else
    2. x equals Object if self == main, else
    3. x equals self.
  2. Gray color indicates that for class variables, the owner is not exactly specified in this document.

Containment revisited

Includer containment is not a first-class structure in Ruby. The .cparent function is not directly supported, and, in general, is not even obtainable. The built-in Module's method x.name provides a global containment path of x in one of the following forms :
  1. A::B::C, if x.croot equals Object,
  2. #<Class:0xaafc40>::A::B::C, otherwise (i.e. if x.croot is an eigenclass).
Getting x.cparent means resolving the path without the last segment. Such a resolution might not yield consistent results, as discussed in the following subsections.

Note: () x.name may be even undefined, as shown in the following section.

Encoding compatibility
We say that containment naming is encoding compatible if
(CNC~0) the pathname estrings x.cancs.map{ |a| a.cname.estr } are (loosely) concatenable for every includer x.
Unfortunately, Ruby 1.9.2 allows incompatibly encoded names of containment ancestors:
X = Object.const_set("X\u00e1".encode('utf-8'), Class.new)
class X
  Y = const_set("Y\u00e1".encode('iso-8859-2'), Class.new)
end
p X::Y.name    # raises Encoding::CompatibilityError
Sibling consistency
We say that containment naming is sibling consistent if .cname is injective on each sibling set, i.e.
(CNC~1) for every includers x, y with a containment parent,
x.cparent == y.cparent and x.cname == y.cname   implies   x == y.
Unfortunately, as of Ruby 1.9, sibling consistency is not satisfied in general, as demonstrated below.
class X
  class Y; end
  Z = Y
  class_eval { remove_const :Y }
  class Y; end
end
x = X
y = X::Y
z = X::Z
puts "#{y.object_id}-->#{y}"
puts "#{z.object_id}-->#{z}"
The code produces three different classes, x, y and z, such that
  1. y.cparent == z.cparent == x, and
  2. y.cname == z.cname == "Y".
This is due to Ruby allowing a rather unrestricted constant removal. The following code shows a possible modification of Module's method remove_const which would prevent, at least to some extent, sibling inconsistencies.
class Module
  alias toS to_s
  alias __remove_const remove_const; private :__remove_const
  def remove_const(sym)
    c = const_defined?(sym, false) ? const_get(sym,false) : nil
    if c.kind_of?(Module) && c.toS.match(Regexp.new("((\\:\\:)|(^))#{sym}$"))
      raise NameError.new(
           "Cannot remove module/class :#{sym} from #{toS}", sym)
    end
    __remove_const(sym)
  end
end
Property consistency
We say that containment naming is (own) property consistent if the following is satisfied:
(CNC~2) x.cparent.pty(x.cname) == x for every includer x with a containment parent.
It is straightforward to see that property consistency implies sibling consistency. There are (at least) 2 ways to break property consistency:
  1. (a) removing a constant (see Sibling consistency),
  2. (b) reassignment of a constant.
While (a) can be prevented by remove_const modification, (b) seems to be preventable only by convention. (When in $VERBOSE mode, Ruby issues a warning about an already initialized constant.)
Resolution consistency
We say that containment naming is resolution consistent if the following is satisfied:
(CNC~3) x ≤ y and y == b.cparent implies x.cst_h(b.cname) ≤ b.
This condition is important if an inner-class coding pattern is applied as outlined by the following example:
class X
  def a; @a end
  def initialize; @a = self.class::A.new end
  class A
    def report; self.to_s end
  end
end
class Y < X
  def initialize; super end
  class A < A; end
end
puts X.new.a.report  #--> #<X::A:0xab80d0>
puts Y.new.a.report  #--> #<Y::A:0xab8028>

A3: Method visibility

An A3 structure is an A2 structure equipped with (.mvisibility()) where

The following axioms are required to hold:

(A3~1*) x.mvisibility(s) is defined  iff  x.met(s) is defined and is not a whiteout.
For x.mvisibility(s) == v we say that the own visibility of s in x is v.

We define inherited visibility .mvisibility_h() by

whenever x.mowner(s) is defined.
Unqualified method resolution
By an unqualified method resolution map we mean a (partial) function uqmr() from Υ to O × Υ defined as the partial application smr(self, _), i.e. If uqmr(s) == (y,t) then we say that (y,t) is an unqualified method resolution of s.

The uqmr map can be expressed as a method of Object as follows:

class Object
  def uqmr(s)
    t = :method_missing
    respond_to?(s, true) ? [method(s).owner, s] :
    respond_to?(t, true) ? [method(t).owner, t] : nil
  end
end
Qualified method resolution
By a qualified method resolution map we mean a (partial) function qmr() from O × Υ to O × Υ defined as follows.
  1. (1) qmr(x,s) == (x.ec.mowner(s), s) if
    1. (a) x.ec.mvisibility_h(s) is :public, or
    2. (b) x.ec.mvisibility_h(s) is :protected and self.ec ≤ x.ec.mowner(s), else
  2. (2) qmr(x,s) == (x.ec.mowner(μ), μ) if x.ec.mowner(μ) is defined, else
  3. (3) qmr(x,s) is undefined.
If qmr(x,s) == (y,t) then we say that (y,t) is a qualified method resolution of (x,s).

Notes:


The qmr map can be expressed as a method of Object as follows:

class Object
  def qmr(x,s)
    if x.respond_to?(s, false)   # public or protected
      o = x.method(s).owner
      if o.protected_instance_methods(false).include?(s) && !kind_of?(o)
         o = nil
      end
      if o then return [o, s] end
    end
    t = :method_missing
    x.respond_to?(t, true) ? [x.method(t).owner, t] : nil
  end
end
Transitions
In contrast to the .met() and .pty() partial maps, .mvisibility() is not guaranteed to be preserved on frozen objects:
class X; def m; end
end
p X.private_instance_methods(false) # []

X.freeze

class X; private :m
end
p X.private_instance_methods(false) # [:m]

Modifications of .mvisibility() are accomplished according to the following table.
Transition parameters Requested output condition Ruby correspondents
(visibility specifiers)
Method visibility setting
(1)
  1. x, the requested owner,
  2. s, the requested method name,
  3. v, the requested method visibility.
x.mvisibility'(s) == v
  1. private(s),
  2. protected(s),
  3. public(s).
(2)
  1. x, the object with x.ec the requested owner,
  2. s, v as in (1).
x.ec.mvisibility'(s) == v
  1. x.private_class_method(s),
  2. x.public_class_method(s).

Notes:

  1. If x is not specified in the rightmost column, then x equals self unless self == main – in this case x == Object.
  2. v is implied by the method (visibility specifier) used.
Disowned visibility
The asterisk in (A3~1*) indicates that the condition is not guaranteed in MRI 1.9. It can be broken using private_class_method or public_class_method visibility specifiers:
class X; end
class << X
  private; def m; end
end
ec = (x = X.new).singleton_class

p ec.respond_to?(:m)                                 # false
  ec.public_class_method(:m)
p ec.respond_to?(:m)                                 # true
p ec.method(:m).owner                                # X.ec (#<Class:X>)

p ec.singleton_methods(false)                        # [:m]
p ec.singleton_class.public_instance_methods(false)  # []
p ec.singleton_methods(false)                        # []
The visibility of :m in x.ec.ec has been changed from private to public but the owner of :m remains X.ec (according to ec.method(:m).owner). In addition, the last three line show inconsistency in :m's ownership.

A4: Arrows

An A4 structure is an A3 structure equipped with (Α, ϙ(), ϻ(), ι(), ϰ(), α(), β(), αɦ(), βɦ()) where
ϙ() is a partial map from O × to Α, ϙ(x,s) is defined according to the table in Internal property arrows.
ϻ() is a partial map from O × to Α, ϻ(x,i) is defined iff x.incs[i] is defined.
ι() is a partial map from Arrays × to Α, ι(x,i) is defined iff x._list[i] is defined.
ϰ() is a partial map from Hashes × to Α, ϰ(x,i) is defined iff x.keys[i] is defined.
α() is a partial map from O × Υ to Α, α(x,s) is defined iff x.met(s) is defined.
β() is a partial map from O × Υ to Α, β(x,s) is defined iff x.pty(s) is defined.
αɦ() is a partial map from O × Υ to Α, αɦ(x,s) is defined iff x is actual and x.ec.mowner(s) is defined.
βɦ() is a partial map from O × Υ to Α, βɦ(x,s) is defined iff x is actual and x.powner(s) is defined.
The following table provides a summary of arrow nomenclature together with applicability of arrow constituents.
Symbol
s
arrow constituents Source Name (Key) Owner Attributes Target
Terminology for the set Αs source idx hcode key / name owner mvisibility target
ϙ internal-property-arrows internal
arrows
ϻ inclusion-list-arrows
ι array-arrows (external)
own-arrows
ϰ hash-arrows
α own-method-arrows
β own-property-arrows
αɦ preview-method-arrows preview-arrows
βɦ preview-property-arrows
Internal arrows
We will not use ω-objects for our arrow formalization. Instead, we use the set O of objects directly, so that the ωobjects table can be considered split again into (A) objects and (B) a table of module inclusion lists. This yields together considered as internal arrows.
Internal property arrows
We consider internal property arrows be abstractions of triples (source, name, target) according to the following table.
Terminology source
Domain
name target
Domain
Description
self-arrows objects self (*) objects identity arrow
type-system-arrows objects terminative? boolean basic type
primary? boolean
non-terminals sc non-terminals superclass
eigenclasses ce objects eigenclass predecessor
objects ec eigenclasses eigenclass successor
includers cparent includers containment parent
cname symbols (Υ) containment name
common-value-arrows objects frozen? boolean frozenness
tainted? taintedness
trusted? trust
special-value-arrows some
non-includers
value,
bytes,
dflt,
Object value fields, see Object internal value data.

Note: (*) This corresponds to the identity function .self on O. Because of the dot-notation, there is no naming conflict with the already introduced self cursor.

Induced maps
Own data fields
Maps introduced in the previous subsection correspond to the following data fields:
Applies to Field
Name
Domain
(Type)
Description Relevant field(s) in MRI
ϙ-arrows source OID the object
name internal property name
target mixed internal property value
ϻ-arrows source OID the includer
idx member index
target OID ith-includee
ι-arrows source (owner) OID the Array instance (owner)
idx member index
target (value) OID the target object (value)
ϰ-arrows source (owner) OID the Hash instance (owner)
idx member index
hcode hash code
key OID key
target (value) OID the target object (value)
α -arrows source (owner) OID the source object (owner)
name string method name
mvisibility ENUM_V method visibility part of flag (in rb_method_entry_t)
target (value) Π the target method (value) def (in rb_method_entry_t)
β-arrows source (owner) OID the source object (owner)
name string property name
target (value) OID the target object (value)
Preview arrows
The following maps are induced on preview arrows.
Keys
(like with own arrows)
Origin
  • αɦ(x,s).owner x.ec.mowner(s)
  • βɦ(x,s).owner x.powner(s)
Values
  • αɦ(x,s).target x.ec.met_h(s)
  • βɦ(x,s).target x.pty_h(s)
Attributes
  • αɦ(x,s).mvisibility x.ec.mvisibility_h(s)
This gives rise to the αɦarrows and βɦarrows data tables which are sort of built-in database view. Gray color indicates that except for the owner column, αarrows (resp. βarrows) and αɦarrows (βɦarrows) have the same field set.
αɦarrows βɦarrows
source (alias receiver) name owner mvisibility target
     
     
     
source name owner target
    
    
    
Object data stratification
Grouping arrows a emanating from a given object x (i.e. such that a.source == x) we obtain a picture about object data stratification.
Arrow set Subset Description
ϙ-arrows identity arrow Object identity
type-system Internal properties
common values
special values
ϻ-arrows Inclusion list
ι-arrows and ϰ-arrows Array/hash members
α-arrows Own methods
β-arrows Own properties
αɦ-arrows Respondent methods
βɦ-arrows Inherited properties

A5: Object copy

An A5 structure is an A4 structure equipped with (.dyn_class?) where The following is required:
(A5~1) All subclasses of a dynamic class are dynamic.
(A5~2) Of the already introduced classes, only Proc is dynamic.

Using the .dyn_class? attribute, we define the following attributes of objects: The attributes are defined according to the following table.
objects x x.clontype x.dumptype
eigenclasses 'none' 'none'
classes and modules such that x.croot != Object
(in particular, anonymous classes and modules)
immediate values (false, true, nil, Fixnums, Symbols) 'ref'
Encodings
the inheritance root r (i.e. the BasicObject class)
Numerics except Fixnums 'clone'
includers such that x.croot == Object except r
(i.e. full-named classes and modules except BasicObject)
'clone' 'ref'
instances of dynamic classes (Procs, Methods, UnboundMethods, …) 'none'
other objects (objects that are not immediate values and not instances of Module, Class, Encoding, Numeric and of dynamic classes) 'clone'
Object cloning / duplication
The clone and dup methods of Kernel create siblings in the primary inheritance. Given a primary object x (a class or, more typically, a terminal), y = x.clone (or y = x.dup) creates a primary object y such that .terminative? and .ec_sc_pr coincide on x and y. The following table shows a correspondence between clone/dup and new.
The new method The clone method The dup method
Application x is a class Class.new(x.sc) x.clone x.dup
x is a terminal x.class.new x.clone x.dup
Hooks initialize initialize_clone initialize_dup
initialize_copy
Procedure without hooks
  1. Create an eigenclass chain represented by 1 actual object.
  1. Establish .sc-links (via .class-link if x is terminal).
  1. Make a shallow copy: Copy the arrow set x.arrows specified below. In the case of clone, this causes arrowed eigenclasses to become actual. ()
The copied arrow set x.arrows is defined as follows:
clone dup
  • x.arrows is undefined if x.clontype == 'none', else
  • x.arrows is the set of all own arrows a such that
a.source.pr == x a.source == x
except self-arrows and except the following:
  • .ec / .ce arrows, and
  • .cparent / .cname arrows (i.e. clones of includers are anonymous),
  • .frozen? arrows.

Notes:

  1. Cloning / duplicating objects x with x.clontype == 'none' is not supported.
  2. The inheritance root cannot have siblings, thus r.clontype == 'none'.
  3. There seems to be a difference between clone versus dup support for instances of Method and UnboundMethod. This difference is not expressed by the above description.
  4. Hash cloning/duplication is based on sequential insertion of hash members. This might result in a modified set of ϰ-arrows.
  5. () In addition, for each a from x.arrows that is an own method arrow (an α-arrow) the method a.target is copied too. This allows for proper copy of method de-aliasing. To describe this would require introducing arrows for .orig_owner and .orig_name.
Object dumping
We describe Marshal.dump logic by specifying sets x.objects and x.arrows of stored objects and arrows, respectively, for each dumpable object x. We proceed inductively by defining sets x.objects(0), …, x.objects(n) and x.arrows(0), …, x.arrows(n) so that x.objects(n) (resp. x.arrows(n)), if defined, is some subset of objects (resp. arrows) reachable from x by at most n arrows.

The sets x.objects and x.arrows are then either undefined (in the case of a non-dumpable object x) or they are defined by

where n is such that x.objects(n) == x.objects(n+1).
(I)
(II)
Let n > 0.

Comments:

  1. Some arrow paths starting with an .ec-arrow are considered as a single short-cut arrow (case (B)).
  2. If x.dumptype == 'ref' then x.objects is defined as empty set but x.arrows is defined as a singleton set containing just the identity arrow of x. This means that just a reference to x is dumped.
  3. The set x.objects can only contain pure instances (non-includers). Includers are dumped by reference.
  4. Dumping of x is unsupported whenever there is an arrow path ending in an object y with y.objects(0) undefined, in particular, if y.dumptype == 'none'.

A6: Method de-aliasing

An A6 structure is an A5 structure equipped with (.orig_owner, .orig_name) where The following is required:
(A6~1) For every non-whiteout method α,
  • x.met(s) == α   implies   x ≤ α.orig_owner.

Notes:

Transitions
(A6-T~1*) The .orig_owner and .orig_name are preserved.
As of MRI 1.9.2, this condition is not 100% guaranteed, due to implementation bugs []. An example of overwriting the original method owner is shown in the following code.
def def_report
  class_eval { def report; super end }
end
class A;     def report; '-A-' end end
class B;     def report; '-B-' end end
class X < A; def_report;           end
class Y < B; def_report;           end
p Y.new.report           #-> -B-
p X.new.report           #-> NotImplementedError

Note: The example demonstrates that the problem is not restricted to eigenclasses, as suggested by the error message (super from singleton method that is defined to multiple classes is not supported ).

Parent method call (super)
By the parent method resolution we mean a partial function pmr() from O × O × Υ to O × O × Υ. The assignment (x,o,s) (x,p,t) has the following semantics:
  • x is the receiver,
  • o is the current method owner,
  • s is the current method name,
  • x is the preserved receiver,
  • p is the parent method owner,
  • t is the parent method name.
Given a triple (x,o,s), the triple (x,p,t) == pmr(x,o,s) is defined as follows:

Notes:

  1. (B4) shows that the parent method call involves de-aliasing.
  2. Repetitive inclusion lists can induce cycles in the pmr map.

Examples:

Actuality revisited

As of MRI 1.9, Ruby allows hacks to create semiactual objects which do not satisfy blankness conditions imposed by our axioms. In particular, it is possible to have semiactual objects x such that
The .ec_ref hack
The code below defines a method ec_ref which allows to reference an eigenclass x.ec of a class x without x.ec's actualization. The method makes a guess of x.ec's object identifier and uses ObjectSpace._id2ref, see The inverse to .object_id.
class Class
  EC_ID_DELTA = (x = Class.new).singleton_class.object_id - x.object_id
  def ec_ref
    ObjectSpace._id2ref(object_id + EC_ID_DELTA)
  end
end
Though being platform/environment dependent by nature, the code probably works in most cases.
The _class_method hack
Having semiactual method-owners can also be accomplished via the private_class_method and public_class_method visibility specifiers:
class X; end; class Y < X; end
def X.m; end
                    nnt_delta_report
Y.private_class_method(:m)
                    nnt_delta_report  # 0
ec = Y.singleton_class
                    nnt_delta_report  # 1
p ec.private_instance_methods(false)  # [:m]

 

Built-in Ruby reflection

Blank slate objects
Except for very few methods like !, ==, != or equal?, Ruby does not provide reflection for blank slate objects. Most methods that are stated to be provided for objects are actually provided for Objects.

A test whether an object x is not a blank slate object is performed by Object === x (it evaluates to true if x is an Object, i.e. x is not a blank slate object).

The object-to-integer map .object_id
The built-in method object_id of Object provides an injective map from actual objects to integers represented by Fixnum or Bignum instances. Immediate values except Symbols have a fixed value-to-id prescription.
Object x Class of x x.object_id Class of x.object_id
false FalseClass 0 Fixnum
true TrueClass 2
nil NilClass 4
x Fixnum 2*x + 1
Fixnum if 2*x + 1 is in Fixnum's range,
Bignum otherwise
All other objects No fixed prescription Fixnum
The inverse to .object_id
The built-in method ObjectSpace._id2ref(x) provides an inverse to y.object_id:

Note: The method also works for semiactual objects, see The .ec_ref hack.

The .toX map
The .toX function is an injective map from objects to hexadecimally encoded integers. It is of the form where .oid2 is a variant of .object_id. The string "0x2fe00e" is an example of a .toX value.

Note: The 8 digit which is used in the string format determines string length and is platform dependent.

The conversion between .object_id and .oid2 is partially described in the following table.

x.oid2 Applicability condition
x.object_id x is false, true, nil or a non-negative Fixnum
? x is a negative Fixnum
? x is a symbol
2 * x.object_id x is NOT an immediate value
The object-to-string map .toS
The built-in methods to_s of Object and Module provide a map from objects to strings represented by String instances. If (CNC~1) is satisfied then the map is injective.

In contrast to object_id, the to_s method is – by convention – subject to override. We therefore refer to an alias method .toS which can be established by

class Object; alias toS to_s end
class Module; alias toS to_s end
The .toS function is defined according to the following rules:
x.toS Applicability condition
"Object" x == Object
x.cpathname x.croot == Object and x != Object
"#<Class:%s>:%s" %
[x.croot.toX, x.cpathname]
x is a named class or module and x.croot an eigenclass
"#<%s:%s>" % [x.class.toS, x.toX] x is a non-includer (pure instance)
x is an anonymous module
  "#<Class:%s>" % [x.toX] x is an anonymous class
"#<Class:%s>" % [x.ce.toS] x is an eigenclass
The table below contains representative examples of .toS. Each row contains a representantive x of a partition of primary objects according to the following criteria:
  1. Is x a named module?
  2. The includer containment component type (A, B, or C) of x or of x.class if x is a non-includer.
  3. Is x terminal?
We assume that X::Y and X.ec::A are classes and X::M and X.ec::N are modules.
Containment type x.ec(i).toS   (string representation of i-th eigenclass of a primary object x)
x i 0 1
A X::Y X::Y #<Class:X::Y>
X::Y.new #<X::Y:0xab4590> #<Class:#<X::Y:0xab4590>>
B Class.new #<Class:0xab43f8> #<Class:#<Class:0xab43f8>>
    ↳    .new #<#<Class:0xab43f8>:0xab4200> #<Class:#<#<Class:0xab43f8>:0xab4200>>
C X.ec::A #<Class:0xad3d00>::A #<Class:#<Class:0xad3d00>::A>
X.ec::A.new #<#<Class:0xad3d00>::A:0xaaf9a0> #<Class:#<#<Class:0xad3d00>::A:0xaaf9a0>>
named modules
A X::M X::M #<Class:X::M>
C X.ec::N #<Class:0xaae968>::N #<Class:#<Class:0xaae968>::N>

Observations: Denote y = x.ec(i).

  1. Denote n the number of trailing '>' characters of y.toS. Then for the eigenclass index i the following holds:
  2. For i > 0, the substring of y.toS delimited by the last occurrence of a single ':' and the first occurrence of trailing '>' equals
  3. Whether y is terminative cannot be detected from y.toS alone (X.ec::A.toS and X.ec::N.toS have same format).
These observations allow us, for arbitrary object y, to obtain the eigenclass index y.eci and the primary object y.pr from the string representation y.toS. This can be realized by the following code:
class Object
  def eci
    (s = toS).length - s.index(/[>]*$/) - (s.match(/:0x(?!.*\:\:)/)? 1:0)
  end
  def pr_chunk
    (m = toS.match(/[^:]\:(?!\:)(?!.*[^:]\:[^:])(.*[^>])[>]+$/)) ? m[1] : toS
  end
  def pr
    if eci == 0 then return self end
    c = pr_chunk
    if c.include?(">")      # the complicated case:  pr.croot is an eigenclass
      r = (m = c.match(/^(0x.*)[>]::(.*)$/)) ? m[1] : raise("???")
      r = ObjectSpace._id2ref(eval(r)/2)
      return r.class_eval("self::" + m[2])
    end
    (p = ::Object.class_eval(c)).instance_of?(Fixnum) ?
    ObjectSpace._id2ref(p/2) : p
  end
end
Unfortunately, the .pr implementation requires (CNC~2).
Containment reflection
Relying on (CNC~2), we can implement includer containment reflection as follows.
class Module
  def cname
    (m = toS.match(/(^|(::))([^:>]+)$/)) ? m[3] : nil
  end
  def cpathname
    (m = toS.match(/(^Object|^|(::))([^<>]*)$/)) ? m[3] : ""
  end
  def croot_toS
    ((s = toS).match(/^[^>]*$/)) ? "Object" :
    (m = s.match(/(.*?)::.*[^>]$/)) ? m[1] : toS
  end
  def croot
    if eci > 0 then return self end
    n = croot_toS
    m = n.match(/\:(0x[^>]*)/)
    m ? ObjectSpace._id2ref(eval(m[1])/2) : eval("::" + n)
  end
  def cancs
    a = [croot]
    cpathname.split("::").each { |x| a << a.last.const_get(x, false) }
    a.reverse!
  end
  def cparent(i = 1); cancs[i] end
end
Enumerating objects
Ruby's facilities for enumerating the object set O are described in the following table.
Subset of O Way of enumeration
Immediate
values
false, true, and nil Literal enumeration
Fixnums Not supported (?)
Symbols Using Symbol.all_symbols
Classes and terminals that are not immediate values Using ObjectSpace.each_object
Actual eigenclasses Not supported (?)

Note: () Fixnums can be enumerated by domain enumeration. The not supported status means that there is no efficient built-in way (known to the author) of enumerating just the fixnums with non-default state (see Immediate values revisited).

Reference to built-in Ruby reflection
Relations, (partial) functions and constants
defined in this document
Ruby 1.9 built-in or semi-built-in correspondents
Notation Description
x.sc the superclass of a non-terminal x x.superclass
x.ec the eigenclass of x
(the successor of x in the eigenclass chain)
x.singleton_class with the following limitations:
  1. Not defined or not equal to x.ec if x is an immediate value.
  2. With a possible side-effect of making an eigenclass actual.
x.ec(i) i-th eigenclass successor of x i > 0 ? x.singleton_class.ec(i-1) : x
x.ce the predecessor of x in the eigenclass chain Obtainable from x.toS, see x.ce(i).

Note: Internally, x.ce is referenced by an internal instance variable __attached__.

x.ce(i) i-th eigenclass predecessor of x (j = x.eci - i) > 0 ? x.pr.ec(j) : nil
x.pr the primary object of x Obtainable from x.toS using ObjectSpace._id2ref.
x.eci the eigenclass index of x Obtainable from x.toS.
r the inheritance root BasicObject
c
(equal to r.class)
the instance root,
the metaclass root
Class
m the metamodule root Module
¤
(equal to r.croot)
  • the conventional inheritance root
  • the (named) containment root
  • the default creation-superclass
Object
¤.incs[0]
if having its initial value
the conventional MRO root Kernel

Note: More precisely, the conventional MRO root equals the pair (Object,Kernel).

r.ec the implicit-metaclass root BasicObject.singleton_class
c.ec the usual actualclass root Class.singleton_class
x.class the class of x x.class
x direct-instance-of y x.class equal to y x.instance_of? y
x instance-of y x.class is a subclass of or equal to y x.class ≤ y && y.class == Class

Note: x.class ≤ y implies that y is not an eigenclass.

x kind-of y x.ec ≤ y x.kind_of? y
x.incs own inclusion list of an includer x Obtainable via included_modules. Equals
  1. x.included_modules if x is a module or r,
  2. x.included_modules.reverse.drop( x.superclass.included_modules.length).reverse otherwise.
x own-includer-of y includer x is an own includer of a module y
  1. x.include? y if x is a module or r,
  2. x.incs.include? y in general.
Note: x.include?(y) && !x.superclass.include?(y) is not reliable with respect to repetitive ancestor lists.
x includer-of y x.include? y (method of Module)
xy HM descendancy x <= y (method of Module)
Similarly, methods <, >=, > are defined with obvious meaning.
x.hancs inheritance ancestors of a non-terminal x, starting with x itself Obtainable using x.superclass. Equals
  • [x] if x is r,
  • [x] + x.superclass.hancs otherwise.
x.hancestors x.hancs without eigenclasses x.ancestors - x.included_modules
x.ancs MRO ancestors of an includer x, starting with x itself Obtainable using x.superclass and x.incs. Equals
  1. x.ancestors if x is a module or r,
  2. [x] + x.incs + x.superclass.ancs otherwise.
x.ancestors x.ancs without eigenclasses x.ancestors (method of Module)
x.terminal? is an object x terminal? x.class != Class
x.class? is an object x a class? x.class == Class && x == x.ancestors[0]
  • using .toS: x.class == Class && x.eci == 0
x.eigenclass? is an object x an eigenclass? x.class == Class && x != x.ancestors[0]
  • using .toS: x.eci > 0
x.terminative? is an object x terminative?
  • using .toS: x.pr.terminal?
x.metaclass? is an object x a metaclass? Class == x.class && !!(Class >= x)
x.module? is an object x a module? x.kind_of?(Module) && !x.kind_of?(Class)
x.metamodule? is an object x a metamodule? x <= Module && x != Class && x.class?
x.blank_slate? is an object x a blank slate object? !(Object === x)
x.frozen? is an object x frozen? x.frozen?
x.tainted? is an object x tainted? x.tainted?
x.trusted? is an object x trusted? !x.untrusted?
x.klass the virtual connection to x's eigenclass Not supported (?)

Note: Internally, at the C-level, x.klass is referenced by the klass field of the RBasic structure.

x.aclass the actualclass of x Not supported (?)
x.actuals actual eigenclasses-or-self of a primary object x Not supported (?)
x.cparent the containment parent of an includer x See Containment reflection
x.cname the containment name of an includer x
x.cpathname the containment path-name of an includer x
x.croot the containment root of an includer x
x.cancs containment ancestors of an includer x
nesting the current nesting Module.nesting
self the current object self
main the main context Not directly supported (?) but recordable by $main = self at the start of a Ruby program
x nwo-method-owner-of s includer x has own method s which is not a whiteout methods.include?(s)     where methods equals x.private_instance_methods(false) + x.protected_instance_methods(false) + x.public_instance_methods(false) (methods of Module).
x nwo-method-inheritor-of s includer x owns or inherits a non-whiteout method s Same as with nwo-method-owner-of but with (false) omitted (or replaced by (true))
x wo-method-owner-of s (x.met(s) == π) includer x has own whiteout method s Not supported (?)
x wo-method-inheritor-of s (x.met_h(s) == π) includer x inherits a whiteout method s Not supported (?)
x.mowner(s) owner of a non-whiteout method s inherited by an includer x x.instance_method(s).owner
x.ec.mowner(s) owner of a non-whiteout method s inherited by an eigenclass x.ec x.method(s).owner (methods of Object and Method, respectively)
x constant-owner-of s includer x has own constant s x.const_defined?(s, false)
x constant-inheritor-of s includer x owns or inherits constant s x.const_defined?(s, true)
x.cowner(s) owner of a constant s inherited by an includer x x.ancs.find{ |y| y.const_defined?(s, false)}
x.mvisibility(s) own method visibility of s in an includer x
[:private,:protected,:public].find{|v| x.send(
  "#{v}_instance_methods",false).include?(s)
}
x.mvisibility_h(s) inherited method visibility of s in an includer x Same as with .mvisibility but with false replaced by true.
uqcr(s) unqualified constant resolution of s See Unqualified constant resolution
qcr(x,s) qualified constant resolution of (x,s) See Qualified constant resolution
uqmr(s) unqualified method resolution of s See Unqualified method resolution
qmr(x,s) qualified method resolution of (x,s) See Qualified method resolution
pmr(x,o,s) parent method resolution of (x,o,s) Not supported (?)
x.hcodes list of hash-codes of a hash x Not supported (?)
x.keys list of keys of a hash x x.keys
x.values list of values of a hash x x.values
x.dflt the default value/evaluator of a hash x x.default_proc || x.default
x.dflt_call? the .dflt interpretation switch of a hash x !!x.default_proc
Ruby reflection semantics
The following table describes built-in methods for which the semantics has not been (directly) described in the previous subsection.
Method name Owner Semantics
singleton_methods Kernel
  • (A) x.singleton_methods(false).include?(s) iff x.ec.mvisibility(s) is defined and is not :private (in particular, x.ec is an nwo-method-owner of s).
  • (B) x.singleton_methods(true).include?(s) iff
    • x.ec.mowner(s) is defined and appears before x.class in x.ec.ancs (in particular, x.ec.mowner(s) is not a class), and
    • x.ec.mvisibility_h(s) (is defined and) is not :private.
instance_methods Module x.instance_methods(inherit) equals x.protected_instance_methods(inherit) + x.public_instance_methods(inherit) up to member order
Referred Ruby methods
Owner Methods
BasicObject ! == != equal? initialize instance_eval
Object (Kernel) class clone define_singleton_method dup eql? extend freeze frozen? hash initialize_dup initialize_clone initialize_copy instance_of? kind_of? method object_id send singleton_class singleton_methods taint tainted? to_s untaint untrusted?
Module ancestors class_eval const_defined? const_get const_missing const_set constants define_method extended include include? included_modules instance_method instance_methods method_defined? module_eval module_function name private private_class_method private_instance_methods private_method_defined? protected protected_instance_methods protected_method_defined? public public_class_method public_instance_method public_instance_methods public_method_defined? remove_const remove_method to_s undef_method
Module.ec nesting new (b)
Class new superclass
Class.ec new (a)
Kernel global_variables lambda p puts
Array [] + << drop each empty? first include? index join last length map map! pop push replace reverse select shift slice uniq unshift zip
String [] % + bytes chars encode encoding length match to_sym
Encoding ascii_compatible? dummy? name
Hash [] compare_by_identity? default default_proc keys values
Method name owner receiver unbind
UnboundMethod bind name owner receiver
Symbol to_s
Symbol.ec all_symbols
ObjectSpace.ec _id2ref count_objects each_object
Rational denominator numerator
Complex imag real
Range begin end exclude_end?
Marshal.ec dump

Notes:

 

Appendices

Comparison with Smalltalk-80
A comparison of the Ruby object model with the Smalltalk-80 object model is provided in a separate document [].
S1 superstructure representation
Set-theoretic representation of the S1 structure is provided in a separate document []. Objects are embedded into a superstructure of sets so that the S1 structure is modelled by set membership. In particular:

Note: (*) .ec ◦ equals .ec ◦ range-restricted to non-terminals. (In this restriction, no object can be kind-of a module.) The .ec ◦ relation is introduced in the S2 structure and corresponds to the .kind_of? / .is_a? reflection method.

The document also provides alternative axiomatization of S1 structures.

The S1 structure
Since the S1 structure is of fundamental importance not only for Ruby but to object-oriented programming in general, a PDF document [] has been elaborated that is specially dedicated to this structure. In order to describe the implementation of the S1 structure, the document also introduces object actuality. This allows for a brief comparison with the Smalltalk-80 object model.
Object membership
As a related standalone document rather than an appendix, the document [] provides a generalization of S1 and S2 structures that applies to many programming languages. As a result, the fundamental part of the object model is described in a general setting so that [] is a special case. The S1 structure appears as the canonical reduct of Ruby object membership. The kind-of relation is the extended membership which arises by composing the canonical membership with the Μ relation (self-or-own-includer-of).

The document [] uses the symbol for .sc-inheritance (which is called simply inheritance). In addition, both and Μ are reflexive over the whole set of objects, including all terminals.

 

Bibliographic references

Minero Aoki, Ruby Hacking Guide, 2004, (translated by Vincent Isambart, translations and additions by C.E. Thornton, Hawthorne Press 2008, http://www.hawthorne-press.com/WebPage_RHG.html)
Valerie Aurora, Union mounts/writable overlays design, 2009, LWN.net http://lwn.net/Articles/355351/
David A. Black, The Well-Grounded Rubyist, Manning Publications 2009
Egon Börger and Robert Stärk, Abstract State Machines: A Method for High-Level System Design and Analysis, Springer 2003
B.A. Davey, H.A. Priestley, Introduction to lattices and order, Cambridge University Press 2002
(eigenclass.org admin), The double inclusion problem, 2005, http://eigenclass.org/hiki/The+double+inclusion+problem
Patrick Farley, Ruby and otherwise, http://www.klankboomklang.com/category/ruby-internals/
David Flanagan, Yukihiro Matsumoto, The Ruby Programming Language, O'Reilly 2008
Ira R. Forman, Scott H. Danforth, Putting Metaclasses to Work, Addison Wesley 1998
Yuri Gurevich, Evolving algebras: An attempt to discover semantics, in Current trends in theoretical computer science: essays and tutorials, Grzegorz Rozenberg, Arto Salomaa (eds), World Scientific 1993, http://www.eecs.umich.edu/gasm/tutorial/tutorial.html
Yuri Gurevich, Evolving Algebras 1993: Lipari Guide, in Specification and Validation Methods, E. Börger (ed.), Oxford University Press 1995
Vidar Hokstad, The Ruby Object Model - Structure and Semantics, 2009, http://www.hokstad.com/ruby-object-model.html
IPA Ruby Standardization WG, Ruby Draft Specification, 2009, http://ruby-std.netlab.jp/
Yehuda Katz, Ruby's Implementation Does Not Define its Semantics, 2010, http://yehudakatz.com/2010/02/25/rubys-implementation-does-not-define-its-semantics/
John Mair, The Secret Life Of Singletons, 2008, http://banisterfiend.wordpress.com/2008/10/25/the-secret-life-of-singletons/
Craig McMillan, Ruby objects, classes and eigenclasses, 2008, http://mccraigmccraig.wordpress.com/2008/10/29/ruby-objects-classes-and-eigenclasses/
(nLab authors), nLab tree, 2011, http://ncatlab.org/nlab/show/tree#as_digraphs_6
Ondřej Pavlata, The Linux VFS Model: Naming structure, 2011, http://www.atalon.cz/vfs-m/linux-vfs-model/
Ondřej Pavlata, The Ruby Object Model: Comparison with Smalltalk-80, 2012, http://www.atalon.cz/rb-om/ruby-object-model/co-smalltalk/
Ondřej Pavlata, The Ruby Object Model: S1 superstructure representation, 2012, http://www.atalon.cz/rb-om/ruby-object-model/s1-rep/
Ondřej Pavlata, Ruby Object Model – The S1 structure, 2012, http://www.atalon.cz/rb-om/ruby-object-model/rb-om-s1.pdf
Ondřej Pavlata, Object Membership: The core structure of object-oriented programming, 2012, http://www.atalon.cz/om/object-membership/
Paolo Perrotta, Metaprogramming Ruby, Pragmatic Bookshelf 2010
Runpaint, Read Ruby, http://ruby.runpaint.org
Ruby Doc, http://ruby-doc.org
Ruby Forum, http://www.ruby-forum.com
Michele Simionato, The Python 2.3 Method Resolution Order, 2003, http://www.python.org/2.3/mro.html
Dave Thomas, The Ruby Object Model and Metaprogramming, 2008, http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming
Wikipedia: The Free Encyclopedia, http://wikipedia.org

 

Browser compatibility
To be viewed correctly, this document requires advanced browser features to be supported, including
Document history
March22011 The initial release.
September22011 Major update. Main changes:
  • Subtitle changed from Basic structure in detail to Data structure in detail.
  • Built-in data types (Strings, Arrays, Hashes, Numerics, Ranges) introduced.
  • Value domain introduced.
  • The .culturality attribute abandoned.
  • The concept of arrows further developed.
  • The trusted? attribute added.
  • Object copy introduced.
October182011 Improved description of module inclusion.
January22012
  • New section: Method de-aliasing, including the super logic.
  • Extended description of includer containment transitions.
January42012 An example of parent method resolution added.
January112012
  • An elaborated example of an S2 structure added, including SVG pictures.
  • The orientation of inclusion list order changed to coincide with that of ancestor lists.
  • The term pure instance introduced as a synonym to non-includer.
  • Notes to inclusion methods added.
Febuary102012
  • Added: monounary algebra, pseudotree, the S1₀₁ structure, .ec-.aclass interchangeability, conventional actuality.
  • New appendix: Comparison with Smalltalk-80.
March22012
  • The .terminative? attribute removed from S1 signature, (S1~1) and (S1~2) interchanged.
  • New appendix: S1 superstructure representation.
April32012 Renumbering of S4~ conditions so that conditions that only depends on S1 are listed first.
April232012 New appendix: The S1 structure (a PDF article).
April272012 Note about Class.ec not being an owner of new.
June212012
  • Enhanced terminology for metaclasses (distinguishing between explicit a implicit).
  • Introducing the c symbol for the Class class.
June282012 Corrected & improved description of constant resolution (qcr/uqcr).
June292012 The definition of a primorder algebra introduced explicitly (moved from []).
October102012 A reference to object membership [] added.
License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License.