CORE - Literal Instantiation breaks Object Model

  • Thread starter Ilias Lazaridis
  • Start date
I

Ilias Lazaridis

class String
alias_method :eek:rig_initialize, :initialize
def initialize(val)
orig_initialize "OBSERVED: " + val
end
def my_method_test
print self.inspect, " test\n"
end
end

oo_string = String.new("The OO String")
li_string = "The Literal String"

print "Class: ", oo_string.class, " - content: ", oo_string, "\n"
print "Class: ", li_string.class, " - content: ", li_string, "\n"
oo_string.my_method_test
li_string.my_method_test

#OUTPUT
#=> Class: String - content: OBSERVED: The OO String
#=> Class: String - content: The Literal String
#=> "OBSERVED: The OO String" test
#=> "The Literal String" test

-

The behaviour of the class String has been altered, whilst using the
standard mechanisms of the Object Model.

To my huge surprise, although the li_string has been instantiated as
an object of class String, the new initialize method was not called.

This is essentially a defect, as the consistency of the Object Model
breaks.

The statement "Everything is an Object" becomes invalid, because e.g.
a string object instantiated from a literal behaves differently that a
string object instantiated normally via new() (although they share the
same class, and thus should behave the same).

My understanding is, that this is a know-issue and a trade-off due to
performance issues.

The questions are:

b) Is there any way to track (intercept) the instantiation of objects
(especially those instantiated from literals)
1) without a C-level extension
2) with a C-level extension

The interception can be post instantiation.

Underlying Requirement:

Ability to track instantiation of every object within the system.

..
 
M

Matthias Wächter

class String
alias_method :eek:rig_initialize, :initialize
def initialize(val)
orig_initialize "OBSERVED: " + val
end
def my_method_test
print self.inspect, " test\n"
end
end

oo_string = String.new("The OO String")
li_string = "The Literal String"

print "Class: ", oo_string.class, " - content: ", oo_string, "\n"
print "Class: ", li_string.class, " - content: ", li_string, "\n"
oo_string.my_method_test
li_string.my_method_test

#OUTPUT
#=> Class: String - content: OBSERVED: The OO String
#=> Class: String - content: The Literal String
#=> "OBSERVED: The OO String" test
#=> "The Literal String" test

If that really had worked the way you’d expect it, you had ended up in an endless loop running out of stack levels soon. The reason for this is that
each string literal would have to go through your special initialization routine, including your "The OO String" and, very subtle, the "OBSERVED: "
string as well. The former would make oo_string into having two times the "OBSERVED: " prefix, once for initializing the string literal, and second
inside of your explicit String.new() call. But the fact that "OBSERVED: " would require string initialization as well, would lead to an endless loop.

To fix this infinite loop, one would have to initialize a string object prior to hooking initialize() like this:

observed_prefix = "OBSERVED: "

class String
alias_method :eek:rig_initialize, :initialize
def initialize(val)
orig_initialize observed_prefix + val
end
[…]
end

As Matz already pointed out, there are quite some good reasons for letting the parser translate literal constants into objects independent from the
actual program flow.
The behaviour of the class String has been altered, whilst using the
standard mechanisms of the Object Model.

To my huge surprise, although the li_string has been instantiated as
an object of class String, the new initialize method was not called.

This is essentially a defect, as the consistency of the Object Model
breaks.

Another question could be why »"OBSERVED: " + val« does not call initialize() on the resulting string right before that result is passed to
orig_initialize. OTOH, this would lead to another endless loop.
My understanding is, that this is a know-issue and a trade-off due to
performance issues.

As you see from the above, it’s not just a trade-off for performance reasons.
Underlying Requirement:
Ability to track instantiation of every object within the system.

This is an interesting idea. I think you have to patch NEWOBJ() in the sources, at least.

– Matthias
 
C

Christopher Dicely

This is essentially a defect, as the consistency of the Object Model
breaks.

The statement "Everything is an Object" becomes invalid, because
e.g. a string object instantiated from a literal behaves differently that a
string object instantiated normally via new() (although they share the
same class, and thus should behave the same).

The logic in this argument is fundamentally flawed. "Everything is an
Object" does not mean "every object of the same class behaves the
same". Objects of the same class instantiated with different syntax
regularly behave differently. Aside from literal vs. new, you have:

#allocate vs. #new generally
creation via other methods specific to a particular class (e.g., for
Procs objects, proc {} vs. lambda {} vs. Proc.new vs ->() {} vs.
method() vs. & to capture the block passed to a method)

Others in the thread have explained adequately the practical reasons
why you can't have <literal> be interpreted as
<some-class>.new(<literal>) where both <literals> are identical [as
that would produce infinite recursion], but aside from the
practicality you are wrong on the basic theory: different
initialization mechanism producing objects which are discernably
different in the path of method calls involved in their instantiation
and/or their behavior later in their lifecycle is not inconsistent
with Ruby's object model, it is rather fundamental to it.
 
I

Ilias Lazaridis

#change to something like:

orig_initialize val
puts self.inspect
   end
   def my_method_test
     print self.inspect, " test\n"
   end
end
[...]

If that really had worked the way you’d expect it, you had ended up in an endless loop running out of stack levels soon. The reason for thisis that
[...] - (elaborations of recursion problems with current showcase)

I've understood your elaborations.

You should not focus on the showcase code, which could be easily
rewritten to not fall into this "trap" at all (e.g. placing a simple
"puts object.inspect" in the constructor).

Please see the reply to Mr. Matsumoto for more details on the essence
of this issue (and the general essence subjecting development).
As you see from the above, it’s not just a trade-off for performance reasons.

The performance problem is the central one. Everything else comes
after, and should be solvable easily.
This is an interesting idea. I think you have to patch NEWOBJ() in the sources, at least.

Is "NEWOBJ()" the central function, used system-wide (even from the
parser)?

..
 
I

Ilias Lazaridis

class String
  alias_method :eek:rig_initialize, :initialize
  def initialize(val)
    orig_initialize "OBSERVED: " + val
  end
  def my_method_test
    print self.inspect, " test\n"
  end
end

oo_string = String.new("The OO String")
li_string = "The Literal String"

print "Class: ", oo_string.class, " - content: ", oo_string, "\n"
print "Class: ", li_string.class, " - content: ", li_string, "\n"
oo_string.my_method_test
li_string.my_method_test

#OUTPUT
#=> Class: String - content: OBSERVED: The OO String
#=> Class: String - content: The Literal String
#=> "OBSERVED: The OO String" test
#=> "The Literal String" test

-

The behaviour of the class String has been altered, whilst using the
standard mechanisms of the Object Model.

To my huge surprise, although the li_string has been instantiated as
an object of class String, the new initialize method was not called.

This is essentially a defect, as the consistency of the Object Model
breaks.

The statement "Everything is an Object" becomes invalid, because e.g.
a string object instantiated from a literal behaves differently that a
string object instantiated normally via new() (although they share the
same class, and thus should behave the same).

My understanding is, that this is a know-issue and a trade-off due to
performance issues.

The questions are:

b) Is there any way to track (intercept) the instantiation of objects
(especially those instantiated from literals)
  1) without a C-level extension
  2) with a C-level extension

The interception can be post instantiation.

Underlying Requirement:

Ability to track instantiation of every object within the system.

Related Issue:

Literal Instantiation breaks Object Model
http://redmine.ruby-lang.org/issues/4893

Related Issue (Attempt to workaround)

Provide Class#cb_object_instantiated_from_literal(object)
http://redmine.ruby-lang.org/issues/4845

..
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,189
Members
46,735
Latest member
HikmatRamazanov

Latest Threads

Top