Charles Strahan
2010-Sep-24 06:40 UTC
[Ironruby-core] Inheritance in IronRuby - possibly a bug or two?
I have a couple questions about deriving from C# class from IronRuby. For
context, here''s a code example that I will refer to here in a bit:
=======================================
using System;
using System.Reflection;
using IronRuby;
using IronRuby.Runtime;
using Microsoft.Scripting.Hosting.Providers;
namespace Example1
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
class Program
{
private static readonly string _rubyScript = @"
class Person2 < Example1::Person
def initialize(first, last)
super(first, last)
end
end
Person2.new(""Foo"", ""Bar"")
";
static void Main(string[] args)
{
var runtime = Ruby.CreateRuntime();
var engine = runtime.GetEngine("rb");
var context (RubyContext)HostingHelpers.GetLanguageContext(engine);
var scope = engine.CreateScope();
runtime.LoadAssembly(typeof(Program).Assembly);
engine.Execute(_rubyScript, scope);
Console.WriteLine(". . .");
Console.ReadKey(true);
}
}
}
=======================================
If you run that code, you''ll get the following:
InvalidOperationException: can''t allocate class `Person2'' that
derives from
type `Example1::Person'' with no default constructor; define Person2#new
singleton method instead of Person2#initialize
Is this a bug, or is this intended behavior? If this is intentional, then I
think there''s still a different problem: try adding this default
constructor
and then run the code:
public Person()
{
Console.WriteLine("Uhmmm... what did IronRuby do with
`super(first, last)`?");
}
So, the `super(first, last)` still get''s executed... but what did it
do? It
obviously didn''t forward those arguments to the non-default
constructor...
So, I think that means we have one, or possibly two, bugs.
Back to the first question: Wouldn''t it be possible to determine the
correct
constructor to invoke based on the arguments, thus avoiding the exception? I
would imagine that the generated/emitted subclass could contain all of the
same constructors that the base type has, just passing the arguments on to
the base class''s corresponding constructor (I hope that made sense -
sorta
tricky to word that correctly).
By the way, how should I specify that I _don''t_ want the Ruby code to
be
interpreted? I noticed that the debugger broke
in Microsoft.Scripting.Interpreter.Interpreter... maybe that could be part
of the problem.
Cheers,
-Charles
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://rubyforge.org/pipermail/ironruby-core/attachments/20100924/a1ddd5e8/attachment-0001.html>
Tomas Matousek
2010-Sep-24 07:38 UTC
[Ironruby-core] Inheritance in IronRuby - possibly a bug or two?
This behavior is actually by design.
The error message says: "... define Person2#new singleton method instead of
Person2#initialize" like so:
class Person2 < Example1::Person
def self.new(first, last)
super(first, last)
end
end
person = Person2.new("Foo", "Bar")
p person.first_name, person.last_name
We do pick up the superclass''s constructors if you don''t
specify no initialize method, so this works too:
class Person2 < Person
end
person = Person2.new("Foo", "Bar")
p person.first_name, person.last_name
The reason why this doesn''t work when you define initialize is to make
you aware of the fact that your initialize method doesn''t do what you
might expect, that is it doesn''t call the constructor of the
superclass.
If you define a default ctor in the superclass and you have initialize method in
the subclass, the constructor gets invoked to create the object and initialize
is called to initialize it. This patter is kind of close to Ruby semantics.
So you basically have 3 options:
- Do not define new nor initialize => the ctors from base class are
available for construction of the subclass
- Define singleton new => you can choose which base ctor is called.
- The base class has a default ctor and the subclass defines initialize
method => the default ctor is always used for object construction and the
initialize is called with the arguments given to "new".
The reason why we chose this design is due to difference between Ruby and CLR
initialization semantics. CLR classes don''t separate allocation
("allocate") from initialization ("initialize") like Ruby
does. CLR has just constructors (which kind of corresponds to Ruby factory
method "new"). Constructors combine allocation and initialization. The
problem with mapping initialize to CLR constructors is that it operates on
"self" that is already allocated before you can do anything (like call
super):
def initialize(first, last)
p self.first_name # self is already an instance of Person2
here, so we must have called some constructor already (the default one if
available)
super(first, last) # what should this do? We
can''t call the constructor again... it''s too late.
end
As for what super(first, last) does in your code ... it calls
"Object#initialize", which in Ruby 1.9.2 has *args parameters and does
nothing:
>>> class X; end
=> nil>>> init = X.instance_method(:initialize)
=> #<UnboundMethod: X(Object)#initialize>>>> init.parameters
=> [[:rest]]>>> X.new.send(:initialize, 1,2,3,4,5)
=> #<X:0x0000056>
Tomas
From: ironruby-core-bounces at rubyforge.org [mailto:ironruby-core-bounces at
rubyforge.org] On Behalf Of Charles Strahan
Sent: Thursday, September 23, 2010 11:40 PM
To: ironruby-core at rubyforge.org
Subject: [Ironruby-core] Inheritance in IronRuby - possibly a bug or two?
I have a couple questions about deriving from C# class from IronRuby. For
context, here''s a code example that I will refer to here in a bit:
=======================================
using System;
using System.Reflection;
using IronRuby;
using IronRuby.Runtime;
using Microsoft.Scripting.Hosting.Providers;
namespace Example1
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
class Program
{
private static readonly string _rubyScript = @"
class Person2 < Example1::Person
def initialize(first, last)
super(first, last)
end
end
Person2.new(""Foo"", ""Bar"")
";
static void Main(string[] args)
{
var runtime = Ruby.CreateRuntime();
var engine = runtime.GetEngine("rb");
var context =
(RubyContext)HostingHelpers.GetLanguageContext(engine);
var scope = engine.CreateScope();
runtime.LoadAssembly(typeof(Program).Assembly);
engine.Execute(_rubyScript, scope);
Console.WriteLine(". . .");
Console.ReadKey(true);
}
}
}
=======================================
If you run that code, you''ll get the following:
InvalidOperationException: can''t allocate class `Person2'' that
derives from type `Example1::Person'' with no default constructor;
define Person2#new singleton method instead of Person2#initialize
Is this a bug, or is this intended behavior? If this is intentional, then I
think there''s still a different problem: try adding this default
constructor and then run the code:
public Person()
{
Console.WriteLine("Uhmmm... what did IronRuby do with
`super(first, last)`?");
}
So, the `super(first, last)` still get''s executed... but what did it
do? It obviously didn''t forward those arguments to the non-default
constructor...
So, I think that means we have one, or possibly two, bugs.
Back to the first question: Wouldn''t it be possible to determine the
correct constructor to invoke based on the arguments, thus avoiding the
exception? I would imagine that the generated/emitted subclass could contain all
of the same constructors that the base type has, just passing the arguments on
to the base class''s corresponding constructor (I hope that made sense -
sorta tricky to word that correctly).
By the way, how should I specify that I _don''t_ want the Ruby code to
be interpreted? I noticed that the debugger broke in
Microsoft.Scripting.Interpreter.Interpreter... maybe that could be part of the
problem.
Cheers,
-Charles
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://rubyforge.org/pipermail/ironruby-core/attachments/20100924/3ac0ab81/attachment-0001.html>
Charles Strahan
2010-Sep-24 09:04 UTC
[Ironruby-core] Inheritance in IronRuby - possibly a bug or two?
Ah, the semantics do make sense now.
In that case, perhaps there''s one more solution:
class PersonShim < Example1::Person
def self.new(first, last)
Example1::Person.new(first, last)
end
end
class Person2 < PersonShim
def initialize(first, last)
super(first, last)
end
end
Person2.new(""Foo"", ""Bar"")
In my case, I need to make sure that the C# types that I expose to IronRuby
behave almost identically to what you would expect with a pure Ruby
implementation. If you remember back when I was talking about my game engine
clone, I need to make sure that I don''t break user''s scripts -
so I need to
preserve the "expected" semantics of `initialize`. What I could do is
end my
C# classes with "Impl", and write a corresponding Ruby class that
invokes
the correct constructor in `new` (ex: SpriteSubclass -> Sprite ->
SpriteImpl). Mind giving me a sanity check on this, Tomas?
I think this should fix the problem I was having with subclassing my Sprite
class - which get''s me one step closer to releasing this beast!
Thank you, Tomas, that was a very thorough explanation.
-Charles
On Fri, Sep 24, 2010 at 2:38 AM, Tomas Matousek <
Tomas.Matousek at microsoft.com> wrote:
> This behavior is actually by design.
>
>
>
> The error message says: ?? define Person2#new singleton method instead of
> Person2#initialize? like so:
>
>
>
> class Person2 < Example1::Person
>
> def self.new(first, last)
>
> super(first, last)
>
> end
>
> end
>
>
>
> person = Person2.new("Foo", "Bar")
>
> p person.first_name, person.last_name
>
>
>
> We do pick up the superclass?s constructors if you don?t specify no
> initialize method, so this works too:
>
>
>
> class Person2 < Person
>
> end
>
>
>
> person = Person2.new("Foo", "Bar")
>
> p person.first_name, person.last_name
>
>
>
> The reason why this doesn?t work when you define initialize is to make you
> aware of the fact that your initialize method doesn?t do what you might
> expect, that is it doesn?t call the constructor of the superclass.
>
>
>
> If you define a default ctor in the superclass and you have initialize
> method in the subclass, the constructor gets invoked to create the object
> and initialize is called to initialize it. This patter is kind of close to
> Ruby semantics.
>
>
>
> So you basically have 3 options:
>
> - Do not define new nor initialize => the ctors from base class
> are available for construction of the subclass
>
> - Define singleton new => you can choose which base ctor is
> called.
>
> - The base class has a default ctor and the subclass defines
> initialize method => the default ctor is always used for object
construction
> and the initialize is called with the arguments given to ?new?.
>
>
>
> The reason why we chose this design is due to difference between Ruby and
> CLR initialization semantics. CLR classes don?t separate allocation
> (?allocate?) from initialization (?initialize?) like Ruby does. CLR has
just
> constructors (which kind of corresponds to Ruby factory method ?new?).
> Constructors combine allocation and initialization. The problem with
mapping
> initialize to CLR constructors is that it operates on ?self? that is
already
> allocated before you can do anything (like call super):
>
>
>
> def initialize(first, last)
>
> p self.first_name # self is already an instance of
> Person2 here, so we must have called some constructor already (the default
> one if available)
>
> super(first, last) # what should this do? We can?t
> call the constructor again? it?s too late.
>
> end
>
>
>
>
>
> As for what super(first, last) does in your code ? it calls
> ?Object#initialize?, which in Ruby 1.9.2 has *args parameters and does
> nothing:
>
>
>
> >>> class X; end
>
> => nil
>
> >>> init = X.instance_method(:initialize)
>
> => #<UnboundMethod: X(Object)#initialize>
>
> >>> init.parameters
>
> => [[:rest]]
>
> >>> X.new.send(:initialize, 1,2,3,4,5)
>
> => #<X:0x0000056>
>
>
>
> Tomas
>
>
>
> *From:* ironruby-core-bounces at rubyforge.org [mailto:
> ironruby-core-bounces at rubyforge.org] *On Behalf Of *Charles Strahan
> *Sent:* Thursday, September 23, 2010 11:40 PM
> *To:* ironruby-core at rubyforge.org
> *Subject:* [Ironruby-core] Inheritance in IronRuby - possibly a bug or
> two?
>
>
>
> I have a couple questions about deriving from C# class from IronRuby. For
> context, here''s a code example that I will refer to here in a bit:
>
>
>
> =======================================>
>
>
> using System;
>
> using System.Reflection;
>
> using IronRuby;
>
> using IronRuby.Runtime;
>
> using Microsoft.Scripting.Hosting.Providers;
>
>
>
> namespace Example1
>
> {
>
> public class Person
>
> {
>
> public string FirstName { get; set; }
>
> public string LastName { get; set; }
>
>
>
> public Person(string firstName, string lastName)
>
> {
>
> FirstName = firstName;
>
> LastName = lastName;
>
> }
>
> }
>
>
>
> class Program
>
> {
>
> private static readonly string _rubyScript = @"
>
>
>
> class Person2 < Example1::Person
>
> def initialize(first, last)
>
> super(first, last)
>
> end
>
> end
>
>
>
> Person2.new(""Foo"", ""Bar"")
>
>
>
> ";
>
>
>
> static void Main(string[] args)
>
> {
>
> var runtime = Ruby.CreateRuntime();
>
> var engine = runtime.GetEngine("rb");
>
> var context >
(RubyContext)HostingHelpers.GetLanguageContext(engine);
>
> var scope = engine.CreateScope();
>
> runtime.LoadAssembly(typeof(Program).Assembly);
>
>
>
> engine.Execute(_rubyScript, scope);
>
>
>
> Console.WriteLine(". . .");
>
> Console.ReadKey(true);
>
> }
>
> }
>
> }
>
>
>
>
>
> =======================================>
>
>
>
>
> If you run that code, you''ll get the following:
>
>
>
> InvalidOperationException: can''t allocate class `Person2''
that derives from
> type `Example1::Person'' with no default constructor; define
Person2#new
> singleton method instead of Person2#initialize
>
>
>
> Is this a bug, or is this intended behavior? If this is intentional, then
> I think there''s still a different problem: try adding this default
> constructor and then run the code:
>
>
>
> public Person()
>
> {
>
> Console.WriteLine("Uhmmm... what did IronRuby do with
> `super(first, last)`?");
>
> }
>
>
>
> So, the `super(first, last)` still get''s executed... but what did
it do? It
> obviously didn''t forward those arguments to the non-default
constructor...
>
>
>
> So, I think that means we have one, or possibly two, bugs.
>
>
>
> Back to the first question: Wouldn''t it be possible to determine
the
> correct constructor to invoke based on the arguments, thus avoiding the
> exception? I would imagine that the generated/emitted subclass could
contain
> all of the same constructors that the base type has, just passing the
> arguments on to the base class''s corresponding constructor (I hope
that made
> sense - sorta tricky to word that correctly).
>
>
>
>
>
> By the way, how should I specify that I _don''t_ want the Ruby code
to be
> interpreted? I noticed that the debugger broke
> in Microsoft.Scripting.Interpreter.Interpreter... maybe that could be part
> of the problem.
>
>
>
> Cheers,
>
> -Charles
>
> _______________________________________________
> Ironruby-core mailing list
> Ironruby-core at rubyforge.org
> http://rubyforge.org/mailman/listinfo/ironruby-core
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://rubyforge.org/pipermail/ironruby-core/attachments/20100924/dc101855/attachment.html>