Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Language IDEs » Objectteams » Multiple inheritance in OT/J
Multiple inheritance in OT/J [message #517633] Mon, 01 March 2010 15:12 Go to next message
Eugene Hutorny is currently offline Eugene Hutorny
Messages: 109
Registered: January 2010
Senior Member
While reading Kasper's thesis I discovered that OT/J supports dual inheritance.
I've reread OTJLD§1.3 again and found only one statement that may imply dual inheritance §1.3.2:
OTJLD

In addition to implicit inheritance, roles may also inherit using the standard Java keyword extends.


which, from the first reading, I actually (mis)understood as "both implicit and explicit inheritance available but only one can be used".

I've made some experiments to see how you have solved the diamond problem, please clarify if my findings are correct:

1.OT/J uses 'copy inheritance', which, in regards to the diamond problem, is equivalent to virtual inheritance in C++.
2.Methods from both tsuper and super are merged into this class
3.Method names resolved in the following order: this, tsuper, super
4.Constructors are resolved in the following order: this, super

Also, there were some unclear situations.

public team class AT {
	public class A {
		public A(String aname) {
			name = name + aname;
		}
		public String name = "";
		// ...
	}
	public class B extends A {
		public B() {
			super("AT.B");
		}
		// ...
	}
	public class C extends A {
		/* public C() { // default constructor is commented out
			super("AT.C");
		} */
		public C(String aname) {
			super(aname);
		}
		// ...
	}
}


public team class BT extends AT {
	public class B { // ??1 Role inherits incompatible 'extends' declarations: AT.B is not a sub-type of BT.A (OTJLD 1.3.2(b)). 
		public B() {  //  ??2 Implicit super constructor BT.A() is undefined for default constructor. Must define an explicit constructor
			tsuper();		
		}
	}
	public class C extends B {
		public C() { // ??3 AT.C has no default constructor, but compiler does not complain
			super();
			//tsuper("BT.C"); // ??4 Constructor call (tsuper) must be inside a role constructor (OTJLD 2.4.2).
		}
	}
}



??1. I had to define role B in team BT. Not clear why it is needed and why role A is not needed in BT.
??2. Documentation says: Unlike regular inheritance, constructors are also inherited along implicit inheritance, and can be overridden just like normal methods. . The compiler complained about missing constructor BT.B();
??3. AT.C has no default constructor, but compiler did not complain and allowed to create an unitialized instance
??4. There is no way to call tsuper constructor in BT.C

Sources for this test are available here
Re: Multiple inheritance in OT/J [message #517891 is a reply to message #517633] Tue, 02 March 2010 13:16 Go to previous messageGo to next message
Stephan Herrmann is currently offline Stephan Herrmann
Messages: 1007
Registered: July 2009
Senior Member
Hi Eugene,

I admire your example, which trumps our not-so-small test suite.
I have extracted tests and bugs for two problems, see

https://bugs.eclipse.org/304344
https://bugs.eclipse.org/304346

Your issue ??4 is actually blurred by a wrong compiler message.
The correct message should read:
Constructor call (tsuper) must be the first statement in a role constructor (OTJLD 2.4.2)


This leads me to what I see as a misunderstanding behind ??3 and ??4:
We could not forge Java to the degree that a method can contain two
super constructor calls. §2.4.2(a) defines:
Each constructor of a role that is not bound to a base class must use one of this(..), super(..) or tsuper(..).

It should perhaps be stronger by saying "must use exactly one of ...".

Regarding your example this means:
??3: The compiler is correct not to flag this, because the current ctor
overrides the tsuper version and *instead* only calls the explicit super().
Initialization wrt type C is the responsibility of the body of this ctor.

??4: You correctly describe that the tsuper ctor cannot be invoked. This is
correct because that ctor would call the super-ctor from A which would
be wrong in the context of BTeam.C.

More answers:
Quote:
1.OT/J uses 'copy inheritance', which, in regards to the diamond problem, is equivalent to virtual inheritance in C++.

It is equivalent in so far, as indeed no separate versions of a method are
created if it is inherited through several inheritances lines (given a common
root of those lines of inheritance).

Quote:
4.Constructors are resolved in the following order: this, super

I'm not sure in which situation you'd see constructor *lookup* along the super
chain. By contrast, wrt to regular inheritance ctors are (still) statically bound.
Only implicit inheritance supports dynamic binding of constructors.
Given:
team class ATeam {
    public class R0 {}
    public class R1 extends R0 {}
}
team class BTeam extends ATeam { }
R1 createR1(final ATeam t) {
  return new R1<@t>();
}


The allocation expression statically binds to a constructor "R1()",
it can never directly invoke a "R0()" ctor. However, the selection
between ATeam.R1 and BTeam.R1 happens at runtime depending
on the dynamic type of t.

Underlying these rules is the intuition that roles ATeam.R1 and
BTeam.R1 are almost the same, they share the same simple name "R1",
clients cannot see which version is called, tsuper dispatch has
precedence over super etc.

Does this help to clarify?
Stephan

Re: Multiple inheritance in OT/J [message #518130 is a reply to message #517891] Wed, 03 March 2010 08:30 Go to previous messageGo to next message
Eugene Hutorny is currently offline Eugene Hutorny
Messages: 109
Registered: January 2010
Senior Member
Hi Stephan,

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
Hi Eugene,

I admire your example, which trumps our not-so-small test suite.
I have extracted tests and bugs for two problems, see

https://bugs.eclipse.org/304344
https://bugs.eclipse.org/304346


It OT/J you have four kinds of diamonds Smile so you need quadrupled efforts on covering them Smile

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
This leads me to what I see as a misunderstanding behind ??3 and ??4:
We could not forge Java to the degree that a method can contain two super constructor calls. §2.4.2(a) defines:
Each constructor of a role that is not bound to a base class must use one of this(..), super(..) or tsuper(..).

It should perhaps be stronger by saying "must use exactly one of ...".

Does this mean that only one branch of a dual inheritance can be properly constructed with non-default constructors?
BTW, how do you avoid double construction of the common base?
It is a real challenge to resolve all 'diamond construction issues'. When constructing the common base A, its ancestors B and C may use different constructors or different parameters to construct the common base, and thus, the result for one of them would be unexpected - the super exists but has been created in a different way.
The only option I see for this problem require A to be constructed with the default constructors in both branches, e.g. B and C may have whatever constructors, but for every possible constructor in D (BT.C in my test), the A instance should be constructed with the default constructor only on every possible construction path.

I quickly searched if this problem is recognized by the community and found an article: Multiple Inheritance Considered Useful by Jack W. Reeves and my idea correlates with his Recommendation #2.

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
Regarding your example this means:
??3: The compiler is correct not to flag this, because the current ctor
overrides the tsuper version and *instead* only calls the explicit super().
Initialization wrt type C is the responsibility of the body of this ctor.
??4: You correctly describe that the tsuper ctor cannot be invoked. This is
correct because that ctor would call the super-ctor from A which would
be wrong in the context of BTeam.C.

It's hardly fits my brain Smile I'll try to see how it works, may be my eyes will help Smile

Stephan Herrmann wrote on Tue, 02 March 2010 08:16

More answers:
Quote:
4.Constructors are resolved in the following order: this, super

I'm not sure in which situation you'd see constructor *lookup* along the super
chain. By contrast, wrt to regular inheritance ctors are (still) statically bound.


Maybe my wording is not clear. I tried to say that when there is a common root, (such as AT.A in my test), someone (compiler or developer?) has to choose through which path the AT.A constructor is called. In OT/J, according to my observation, compiler chooses super, e.g. the call stack for new BT.C() that reaches AT.A is: BT.C()->super->AT.B()->super->AT.A()
Stephan Herrmann wrote on Tue, 02 March 2010 08:16

Does this help to clarify?

Partly yes Smile

Best Regards,
Eugene
Re: Multiple inheritance in OT/J [message #518199 is a reply to message #518130] Wed, 03 March 2010 13:24 Go to previous message
Stephan Herrmann is currently offline Stephan Herrmann
Messages: 1007
Registered: July 2009
Senior Member
Hi Eugene,

Eugene Hutorny wrote on Wed, 03 March 2010 03:30

It OT/J you have four kinds of diamonds Smile so you need quadrupled efforts on covering them Smile



Four diamonds?? We must be rich! Smile

Quote:

Does this mean that only one branch of a dual inheritance can be properly constructed with non-default constructors?
BTW, how do you avoid double construction of the common base?


(I assume you use "base" referring to "super" here)

The JVM restricts the number of constructor calls (this/super) in a constructor
to exactly 1. This already prevents any double construction.
Now, in order to apply more than a straight path through the
ancestry we have to use some kind of linearization. In fact in my previous
answer I forgot the one rule that makes the trick here (in §1.3.1(i)):
Within role constructors all this(..) and super(..) calls are bound statically with respect to explicit inheritance and dynamically with respect to implicit inheritance. This means the target role name is determined statically, but using that name the suitable role type is determined using dynamic binding. 


So let me illustrate this using the minimal diamond:
team class ATeam {
    protected class R0 {
        protected R0() { System.out.println("ATeam.R0()"); }
   }
    protected class R1 extends R0 {
        protected R1() { System.out.println("ATeam.R1()"); }
   }
}
team class BTeam extends ATeam {
    protected class R0 {
        protected R0() { System.out.println("BTeam.R0()"); }
   }
    protected class R1  {
        protected R1() { System.out.println("BTeam.R1()"); }
   }
}

Creating a BTeam.R1 uses this call-chain:
BTeam.R1() -> BTeam.R0() -> Object()
(compiler always asumes a "super()" call if no ctor-call is specified).

However, if you insert a "tsuper()" into BTeam.R1() then you'll get this chain:
BTeam.R1() -tsuper-> ATeam.R1() -super->BTeam.R0() -super-> Object()

So the tsuper() call binds statically (to ATeam.R1()), however within
that ctor the implicit "super()" call binds dynamically to the most specific
"R0()" applicable in that context. Since creation happens in the context of
a BTeam instance, we use BTeam.R0().

If you also add a tsuper() call to BTeam.R0(), all four constructors are called:
BTeam.R1() -> ATeam.R1() ->BTeam.R0() ->ATeam.R0() -> Object()

Think of roles as a matrix where each row is a team, and each column
is those roles having the same simple name (e.g. "R1"). The compiler
ensures that each column is visited during the call chain (using "super()").
You may freely choose to additionally walk up within a column ("tsuper()")
before proceeding to the next column.

In your original example you constructed a situation where indeed one
ctor was not called and could not be called: tsuper("BT.C") is not legal
within BTeam.C(), because this class changes the inheritance hierarchy
by introducing a new super class. When doing so, the inherited ctor
(from ATeam.C) is no longer applicable as it assumes a direct super class A,
but now C extends B instead.
In this situation class BTeam.C is fully responsible to initialize all aspects
of class C (the column in the matrix). Here it pays off, that BTeam.C
has access even to private fields of ATeam.C, so it can always
initialize those fields, too.

Quote:

It is a real challenge to resolve all 'diamond construction issues'. When constructing the common base A, its ancestors B and C may use different constructors or different parameters to construct the common base, and thus, the result for one of them would be unexpected - the super exists but has been created in a different way.
The only option I see for this problem require A to be constructed with the default constructors in both branches, e.g. B and C may have whatever constructors, but for every possible constructor in D (BT.C in my test), the A instance should be constructed with the default constructor only on every possible construction path.


If I understand you correctly, your suggestions includes graphs of constructor
calls, not a linear sequence. As mentioned the JVM doesn't like that, so we
would have to *awfully* twist the byte code, and would expect users to be
confused having multiple super calls in a ctor, given that all this is still a variant
of Java.

If my premise was wrong please give an example.
Quote:

It's hardly fits my brain Smile I'll try to see how it works, may be my eyes will help Smile


I usually draw pictures to quickly see what's going on Smile
Have you drawn the matrix I described above? Do it! Smile
Quote:

Maybe my wording is not clear. I tried to say that when there is a common root, (such as AT.A in my test), someone (compiler or developer?) has to choose through which path the AT.A constructor is called. In OT/J, according to my observation, compiler chooses super, e.g. the call stack for new BT.C() that reaches AT.A is: BT.C()->super->AT.B()->super->AT.A()


Developer chooses by writing either "super()" or "tsuper()" into each
constructor, but if you write neither, the compiler inserts "super()".
Additionally, if not defining a constructor for a role the compiler helps:
- If a role has a tsuper role the constructor is inherited from that tsuper.
- Otherwise the compiler generates a default ctor calling "super()".

Analysing call stacks is impeded by the fact that we already have to
apply some twist to the byte code, but we try hard to have the debugger
reverse all those translations so I hope that what you saw is actually
what happened. However, the call BT.C()->super->AT.B() looks wrong
to me, because it skips one level (either AT.C() or BT.B()). Perhaps this
step is not very visible in the debugger because it does not relate to
any source lines?

On a more general note: I never was a fan of constructors. I learned my
O-O using Eiffel, and there a clean separation of object creation (with
default values) and custom initialization really makes things easier.
From that point of view the issue of object creation is already flawed in Java
and for OT/J we can only try not to further aggravate the situation.

Stephan
Re: Multiple inheritance in OT/J [message #569383 is a reply to message #517891] Wed, 03 March 2010 08:30 Go to previous message
Eugene Hutorny is currently offline Eugene Hutorny
Messages: 109
Registered: January 2010
Senior Member
Hi Stephan,

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
> Hi Eugene,
>
> I admire your example, which trumps our not-so-small test suite.
> I have extracted tests and bugs for two problems, see
>
> https://bugs.eclipse.org/304344
> https://bugs.eclipse.org/304346

It OT/J you have four kinds of diamonds :) so you need quadrupled efforts on covering them :)

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
> This leads me to what I see as a misunderstanding behind ??3 and ??4:
> We could not forge Java to the degree that a method can contain two super constructor calls. §2.4.2(a) defines:
> Each constructor of a role that is not bound to a base class must use one of this(..), super(..) or tsuper(..).
> It should perhaps be stronger by saying "must use exactly one of ...".

Does this mean that only one branch of a dual inheritance can be properly constructed with non-default constructors?
BTW, how do you avoid double construction of the common base?
It is a real challenge to resolve all 'diamond construction issues'. When constructing the common base A, its ancestors B and C may use different constructors or different parameters to construct the common base, and thus, the result for one of them would be unexpected - the super exists but has been created in a different way.
The only option I see for this problem require A to be constructed with the default constructors in both branches, e.g. B and C may have whatever constructors, but for every possible constructor in D (BT.C in my test), the A instance should be constructed with the default constructor only on every possible construction path.

I quickly searched if this problem is recognized by the community and found an article: http://www.drdobbs.com/cpp/184402074 by Jack W. Reeves and my idea correlates with his Recommendation #2.

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
> Regarding your example this means:
> ??3: The compiler is correct not to flag this, because the current ctor
> overrides the tsuper version and *instead* only calls the explicit super().
> Initialization wrt type C is the responsibility of the body of this ctor.
> ??4: You correctly describe that the tsuper ctor cannot be invoked. This is
> correct because that ctor would call the super-ctor from A which would
> be wrong in the context of BTeam.C.

It's hardly fits my brain :) I'll try to see how it works, may be my eyes will help :)

Stephan Herrmann wrote on Tue, 02 March 2010 08:16
> More answers:
> Quote:
> > 4.Constructors are resolved in the following order: this, super
>
> I'm not sure in which situation you'd see constructor *lookup* along the super
> chain. By contrast, wrt to regular inheritance ctors are (still) statically bound.

Maybe my wording is not clear. I tried to say that when there is a common root, (such as AT.A in my test), someone (compiler or developer?) has to choose through which path the AT.A constructor is called. In OT/J, according to my observation, compiler chooses super, e.g. the call stack for new BT.C() that reaches AT.A is: BT.C()->super->AT.B()->super->AT.A()
Stephan Herrmann wrote on Tue, 02 March 2010 08:16
> Does this help to clarify?

Partly yes :)

Best Regards,
Eugene
Re: Multiple inheritance in OT/J [message #569416 is a reply to message #569383] Wed, 03 March 2010 13:24 Go to previous message
Stephan Herrmann is currently offline Stephan Herrmann
Messages: 1007
Registered: July 2009
Senior Member
Hi Eugene,

Eugene Hutorny wrote on Wed, 03 March 2010 03:30
> It OT/J you have four kinds of diamonds :) so you need quadrupled efforts on covering them :)


Four diamonds?? We must be rich! :)

Quote:
> Does this mean that only one branch of a dual inheritance can be properly constructed with non-default constructors?
> BTW, how do you avoid double construction of the common base?

(I assume you use "base" referring to "super" here)

The JVM restricts the number of constructor calls (this/super) in a constructor
to exactly 1. This already prevents any double construction.
Now, in order to apply more than a straight path through the
ancestry we have to use some kind of linearization. In fact in my previous
answer I forgot the one rule that makes the trick here (in §1.3.1(i)):

Within role constructors all this(..) and super(..) calls are bound statically with respect to explicit inheritance and dynamically with respect to implicit inheritance. This means the target role name is determined statically, but using that name the suitable role type is determined using dynamic binding.

So let me illustrate this using the minimal diamond:
team class ATeam {
protected class R0 {
protected R0() { System.out.println("ATeam.R0()"); }
}
protected class R1 extends R0 {
protected R1() { System.out.println("ATeam.R1()"); }
}
}
team class BTeam extends ATeam {
protected class R0 {
protected R0() { System.out.println("BTeam.R0()"); }
}
protected class R1 {
protected R1() { System.out.println("BTeam.R1()"); }
}
}

Creating a BTeam.R1 uses this call-chain:
BTeam.R1() -> BTeam.R0() -> Object()
(compiler always asumes a "super()" call if no ctor-call is specified).

However, if you insert a "tsuper()" into BTeam.R1() then you'll get this chain:
BTeam.R1() -tsuper-> ATeam.R1() -super->BTeam.R0() -super-> Object()

So the tsuper() call binds statically (to ATeam.R1()), however within
that ctor the implicit "super()" call binds dynamically to the most specific
"R0()" applicable in that context. Since creation happens in the context of
a BTeam instance, we use BTeam.R0().

If you also add a tsuper() call to BTeam.R0(), all four constructors are called:
BTeam.R1() -> ATeam.R1() ->BTeam.R0() ->ATeam.R0() -> Object()

Think of roles as a matrix where each row is a team, and each column
is those roles having the same simple name (e.g. "R1"). The compiler
ensures that each column is visited during the call chain (using "super()").
You may freely choose to additionally walk up within a column ("tsuper()")
before proceeding to the next column.

In your original example you constructed a situation where indeed one
ctor was not called and could not be called: tsuper("BT.C") is not legal
within BTeam.C(), because this class changes the inheritance hierarchy
by introducing a new super class. When doing so, the inherited ctor
(from ATeam.C) is no longer applicable as it assumes a direct super class A,
but now C extends B instead.
In this situation class BTeam.C is fully responsible to initialize all aspects
of class C (the column in the matrix). Here it pays off, that BTeam.C
has access even to private fields of ATeam.C, so it can always
initialize those fields, too.

Quote:
> It is a real challenge to resolve all 'diamond construction issues'. When constructing the common base A, its ancestors B and C may use different constructors or different parameters to construct the common base, and thus, the result for one of them would be unexpected - the super exists but has been created in a different way.
> The only option I see for this problem require A to be constructed with the default constructors in both branches, e.g. B and C may have whatever constructors, but for every possible constructor in D (BT.C in my test), the A instance should be constructed with the default constructor only on every possible construction path.

If I understand you correctly, your suggestions includes graphs of constructor
calls, not a linear sequence. As mentioned the JVM doesn't like that, so we
would have to *awfully* twist the byte code, and would expect users to be
confused having multiple super calls in a ctor, given that all this is still a variant
of Java.

If my premise was wrong please give an example.
Quote:
> It's hardly fits my brain :) I'll try to see how it works, may be my eyes will help :)

I usually draw pictures to quickly see what's going on :)
Have you drawn the matrix I described above? Do it! :)
Quote:
> Maybe my wording is not clear. I tried to say that when there is a common root, (such as AT.A in my test), someone (compiler or developer?) has to choose through which path the AT.A constructor is called. In OT/J, according to my observation, compiler chooses super, e.g. the call stack for new BT.C() that reaches AT.A is: BT.C()->super->AT.B()->super->AT.A()

Developer chooses by writing either "super()" or "tsuper()" into each
constructor, but if you write neither, the compiler inserts "super()".
Additionally, if not defining a constructor for a role the compiler helps:
- If a role has a tsuper role the constructor is inherited from that tsuper.
- Otherwise the compiler generates a default ctor calling "super()".

Analysing call stacks is impeded by the fact that we already have to
apply some twist to the byte code, but we try hard to have the debugger
reverse all those translations so I hope that what you saw is actually
what happened. However, the call BT.C()->super->AT.B() looks wrong
to me, because it skips one level (either AT.C() or BT.B()). Perhaps this
step is not very visible in the debugger because it does not relate to
any source lines?

On a more general note: I never was a fan of constructors. I learned my
O-O using Eiffel, and there a clean separation of object creation (with
default values) and custom initialization really makes things easier.
From that point of view the issue of object creation is already flawed in Java
and for OT/J we can only try not to further aggravate the situation.

Stephan
Previous Topic:Multiple inheritance in OT/J
Next Topic:What makes a class a role?
Goto Forum:
  


Current Time: Thu Oct 02 08:41:34 GMT 2014

Powered by FUDForum. Page generated in 0.03704 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software