Interactions between 'new' and 'initialize'?

In JRuby, I am extending an existing Java Class with a Ruby initialising
block by redefining the ‘new’ class method.
This works fine except for new classes which inherit from the original
class and have a different number of arguments.
These classes generate a ‘wrong number of arguments’ error.

The behavior seems not specific for Java Classes and is actually the
same for Ruby and JRuby.
The following example illustrates it, the first version works as
expected

class Parent
def initialize(y)
puts “I am parent #{y}”
end
end

class Child < Parent
def initialize(c1,c2)
puts “I am a child #{c1} #{c2}”
end
end

Parent.new(‘A’) # => I am parent A
Child.new(‘B’,‘C’) # => I am a child B C

The next example adds a redefinition of Parent.new; Parent goes on to
work as expected, but Child fails:

class Parent
class << self
def new(arg)
puts “redefining new for parent”
super(arg)
end
end
def initialize(y)
puts “I am parent #{y}”
end
end

class Child < Parent
def initialize(c1,c2)
puts “I am a child #{c1} #{c2}”
end
end

Parent.new(‘A’) # => redefining new for Parent
# => I am parent A
Child.new(‘B’,‘C’) # => ArgumentError: wrong number of arguments (2
for 1)

Is this behavior intentional or a bug? At least it is consistent (Ruby
1.8.7, 1.9.1 JRuby 1.6.7)

I have tried using alias instead of super and changing the order of
redefining new and defining initialize. I also tried using class_eval
and taking the Person.new definition out of the original class
definition. All to no avail.
Any help would be appreciated.

Well, you redefined .new to only accept one argument, so what are you
expecting?

Either do not redefine it (I don’t see why is it needed?) or define it
with variable arguments as:

def new(*arg)
puts “redefining new for parent”
super(*arg)
end

– Matma R.

I redefined ‘new’ for Parent, not for Child, so I expected the ‘new’ for
Child to conform with the two arguments taken from the (Child)
initialize method.
I agree there normally is not much reason to redefine the ‘new’ method,
but in this case I am importing a Java Class which I have no chance to
change at source level.

– Cees Z.

Child inherits the redefined method from Parent. You’ll have to
redefine it for Child as well (or monkey-patch the parent).

– Matma R.

Bartosz Dziewoński wrote in post #1066562:

Child inherits the redefined method from Parent. You’ll have to
redefine it for Child as well (or monkey-patch the parent).

– Matma R.

I agree that redefining ‘new’ for Child should work, but I don’t think
Child should inherit ‘new’ from Parent. The arguments to ‘new’ should be
determined by its own ‘initialize’ method which takes two arguments.

In the mean time I think I have solved my problem by patching the
‘initialize’ instance method rather than ‘new’. That at least does not
seem to have any side effects.

==Cees Z.

Cees Z. wrote in post #1066604:

I agree that redefining ‘new’ for Child should work, but I don’t think
Child should inherit ‘new’ from Parent. The arguments to ‘new’ should be
determined by its own ‘initialize’ method which takes two arguments.

Well, “new” is a normal class method, so it will behave just like any
other class methods.

In the mean time I think I have solved my problem by patching the
‘initialize’ instance method rather than ‘new’. That at least does not
seem to have any side effects.

Yes, and it makes much more sense. Redefining “new” seems like a very
bad idea to me, because it will alter the core functionalities of Ruby.
Even if it works, it will be very surprising for other people. It’s like
changing the arithmetic operations in a math program.

Many thanks for all the contributions.

Let me first try to explain a little bit further why I wanted tho change
the ‘new’ class method.
I have recently implemented a set of JRuby bindings for the QtJambi
Library. QtJambi is the Java version of the Qt GUI framework, a
cross-platform application development toolkit.
The library consists of several hundred Java classes and Interfaces. In
JRuby these can all be accessed through the Qt:: namespace.
Classes and Modules are ‘lazily’ loaded when they are accessed through
the ‘const_missing’ method.
In this method a constant is assigned to the underlying Java Class and a
class method ‘new’ is defined similar to the one mentioned in one of the
postings:
def new(*a,&b)
instance=self.allocate
instance.send(:initialize,*args)
if block_given?
if block.arity == -1 || block.arity == 0
instance.instance_eval(&block)
elsif block.arity == 1
block.call(instance)
else
raise ArgumentError, “Wrong number of arguments to
block(#{block.arity} ; should be 1 or 0)”
end
end
return instance
end

The underlying object is an instance of a Java Class, sending an
initialize method is the equivalent of Ruby ‘initialize’. JRuby takes
care of a great deal of ‘type’ conversions between Ruby and Java.
The ‘new’ method also implements an optional Ruby Initializer Block.
Various other things take place upon loading the Java Class, but they
are not directly relevant to this discussion.
Anyone actually interested in the code can download it from
GitHub - CeesZ/qt_connect: Qt bindings for JRuby with (optional) extension to provide backward compatibility with Qt Ruby programs. The API is implemented using Qt Jambi, the Java version of the Qt framework. For Qt Ruby programs it provides a QtJambi inspired interface to the Signals/Slots system without the need to use C++ signatures. or get the Gem: gem install
qt_connect
This takes care of a basic API with access to all Classes and
Interfaces( Modules) in the Qt toolkit. The Java Classes can be extended
with Ruby methods or inherited by Ruby Classes.

Bindings to the Qt toolkit for Ruby (not JRuby) have been available for
many years. Since these bindings and the QtJambi library are based on
the same underlying C++ library the resulting API’s are very similar,
but not quite the same.
To improve compatibility (and program portability) between the two
implementations, I have also added a compatibility layer, which
basically patches individual classes when they are loaded.
These patches are mostly mutations to the input arguments which can be
done by simple Ruby wrappers round the relevant Java Methods. This works
well in practice and many QtRuby programs can now run unchanged on both
platforms.

I ran into a problem however when the arguments of the constructor have
to be patched. I implemented that by wrapping the ‘new’ method (since
that is already a Ruby Method; the initializer is not).
This works fine for the base class, but causes problems for inherited
classes as described in the initial posting.
As far as I can see it, my best bet to solve this problem is to provide
Ruby wrappers for the ‘initialize’ methods and to apply any patches
there.
Thanks to the posting of Robert K. I understand better what happens
when changing a ‘new’ class method, but I can’t say I fully understand
it, so it seems safer not to interfere.
For those interested, I found also a paragraph (13.1.4 Class methods in
-even more- depth) in The Well-Grounded Rubyist from David Black, on the
special behavior on singleton methods defined on objects of class Class.

== Cees Z.

On Sun, Jul 1, 2012 at 3:15 PM, Cees Z. [email protected]
wrote:

Many thanks for all the contributions.

You’re welcome!

class method ‘new’ is defined similar to the one mentioned in one of the
postings:

I haven’t looked at the code, but did you consider a different
solution, like not assigning the Java class to the constant but
something else? For example, you can create an Object instance,
define method new for it (which will create the Java class instance
and do custom initialization) and probably a few others as well (e.g.
superclass, ancestors, :=== etc…). That way you could create a
wrapping layer without the need to fiddle with Java classes. You
might have to add instance methods to get back to your wrapper
“classes” from instances.

Thanks to the posting of Robert K. I understand better what happens
when changing a ‘new’ class method, but I can’t say I fully understand
it, so it seems safer not to interfere.

If you have further questions just let us know.

Kind regards

robert

On Thu, Jun 28, 2012 at 5:30 PM, Cees Z. [email protected]
wrote:

In JRuby, I am extending an existing Java Class with a Ruby initialising
block by redefining the ‘new’ class method.

That does not sound like a good idea. Class#new should only be
overridden if you need to adjust the initialization process. What are
you trying to achieve with that?

This works fine except for new classes which inherit from the original
class and have a different number of arguments.
These classes generate a ‘wrong number of arguments’ error.
The behavior seems not specific for Java Classes and is actually the
same for Ruby and JRuby.
The following example illustrates it, the first version works as
expected

My example

irb(main):001:0> class A;end
=> nil
irb(main):002:0> A.method :new
=> #<Method: Class#new>
irb(main):003:0> class B<A;end
=> nil
irb(main):004:0> B.method :new
=> #<Method: Class#new>
irb(main):005:0> def A.new(x) printf “This won’t work, but anyway %p\n”,
x end
=> nil
irb(main):006:0> A.method :new
=> #<Method: A.new>
irb(main):007:0> B.method :new
=> #<Method: B(A).new>
irb(main):008:0> A.new 1
This won’t work, but anyway 1
=> nil
irb(main):009:0> B.new 2
This won’t work, but anyway 2
=> nil
irb(main):010:0> irb(main):001:0> class A;end
=> #<Method: Class#new>
irb(main):003:0> class B<A;end
=> nil
irb(main):004:0> B.method :new
=> #<Method: Class#new>
irb(main):005:0> def A.new(x) printf “This won’t work, but anyway %p\n”,
x end
=> nil
irb(main):006:0> A.method :new
=> #<Method: A.new>
irb(main):007:0> B.method :new
=> #<Method: B(A).new>
irb(main):008:0> A.new 1
This won’t work, but anyway 1
=> nil
irb(main):009:0> B.new 2
This won’t work, but anyway 2
=> nil

The next example adds a redefinition of Parent.new; Parent goes on to
work as expected, but Child fails:

Method Class#new must always have signature (*a, &b) because otherwise
it cannot properly forward arguments to #initialize. As you can see
this is the case for Class#new already:

irb(main):021:0> Class.method(:new).arity
=> -1

It has to be that way because #initialize of sub classes actually
defines the signature of the constructor. Method Class#new basically
does something like this:

irb(main):001:0> class A
irb(main):002:1> def initialize(x) @x=x end
irb(main):003:1> def self.new(*a,&b)
irb(main):004:2> puts “before”
irb(main):005:2> x = allocate
irb(main):006:2> printf “allocated %p\n”, x
irb(main):007:2> x.send(:initialize, *a, &b)
irb(main):008:2> printf “initialized %p\n”, x
irb(main):009:2> x
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> e = A.new 123
before
allocated #<A:0x80319e78>
initialized #<A:0x80319e78 @x=123>
=> #<A:0x80319e78 @x=123>

There is only reason if you want to change allocation of instances of
a class (e.g. by taking them from a pool of preallocated instances or
such). This is rarely the case.

Is this behavior intentional or a bug? At least it is consistent (Ruby
1.8.7, 1.9.1 JRuby 1.6.7)

I think it is intentional and consistent because if you introduce
specific storage characteristics for a class you’ll likely want all
subclasses to share it. The same happens with singleton methods of
class instances generally:

irb(main):001:0> class A;end
=> nil
irb(main):002:0> class B<A;end
=> nil
irb(main):003:0> A.methods.grep /foo/
=> []
irb(main):004:0> B.methods.grep /foo/
=> []
irb(main):005:0> def A.foo() puts 123 end
=> nil
irb(main):006:0> A.foo
123
=> nil
irb(main):007:0> A.methods.grep /foo/
=> [:foo]
irb(main):008:0> B.methods.grep /foo/
=> [:foo]
irb(main):009:0> B.foo
123
=> nil

That way properties you define on a class are available for subclasses
as well.

irb(main):010:0> class <<A; attr_accessor :bar end
=> nil
irb(main):011:0> A.bar=9
=> 9
irb(main):012:0> B.bar
=> nil
irb(main):013:0> B.bar=123
=> 123
irb(main):014:0> B.bar
=> 123

I have tried using alias instead of super and changing the order of
redefining new and defining initialize. I also tried using class_eval
and taking the Person.new definition out of the original class
definition. All to no avail.
Any help would be appreciated.

Please first explain what your goal is.

Kind regards

robert