class Base {}class Derived extends Base {}class Base2 {public Derived func(int i, Base j) { return null }public Base func2(int i, Derived j) { return null }public Base func3(int i, Base j) { return null }public Derived func4(int i, Derived j) { return null }}class Derived2 extends Base2 {public Derived func(int i, Base j) { return null }public Base func2(int i, Derived j) { return null }public Derived func3(int i, Derived j) { return null }public Base func4(int i, Base j) { return null }public void test() {}}class Derived2b extends Base2 {public Base func(int i, Base j) { return null }public Derived func2(int i, Derived j) { return null }public Base func3(int i, Derived j) { return null }public Derived func4(int i, Base j) { return null }}class Derived2c extends Base2 {public Base func(int i, Derived j) { return null }public Derived func2(int i, Base j) { return null }public Base func3(int i, Base j) { return null }public Derived func4(int i, Derived j) { return null }}class Derived2d extends Base2 {public Derived func(int i, Derived j) { return null }public Base func2(int i, Base j) { return null }public Derived func3(int i, Base j) { return null }public Base func4(int i, Derived j) { return null }}
On 06 May 2016, at 23:56, Matt Browne <notifi...@github.com> wrote:Should trygve be enforcing the Liskov substitution principle? I had heard this principle referenced before, but just today actually learned about it...so I may not understand it fully yet, but it seems that this code should not compile, but it does:
class A { public void foo(int x, int y) {} } class B extends A { public void foo(int x) {} } { //this should compile new A().foo(1, 2); //but this should not, right? new B().foo(1, 2); }
Additionally, I get the following error when running the code:
Error: User process died because of internal trygve error: java.lang.AssertionError
First time (in a good way) I've seen LSP converted to contra/covariance, thanks for that.
> First time (in a good way) I've seen LSP converted to contra/covariance, thanks for that.
Reading the Wikipedia entry (have I not done that before? Probably. I have become addled in the memory. What was I talking about?) I see it is in there via mentioning DbC. I gotta start taking supplements, what stems dementia?
Hmm, it seems that github fooled me with the quoting (again), now that I see in the DCI group that Raoul made those two last posts, not Cope. Cross-posting to github issues creates a little security/identity hole.
—
You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
On 08 May 2016, at 18:46, Greg Young <gregor...@gmail.com> wrote:
This is much more apparent in languages like eiffel.
Hence Sather iirc.
A client using a derived class object through a base class identifier expects the returned objects to at least have the properties of the type returned by the base class method. It may have additional properties (e.g., it may have even more methods in its interface that the corresponding base class return value features). This goes the opposite way of the parameters and is called contravariance.
Covariant method return type[edit]
In a language which allows covariant return types, a derived class can override the
getAnimalForAdoption
method to return a more specific type:class CatShelter extends AnimalShelter { Cat getAnimalForAdoption() { return new Cat(); } }Among mainstream OO languages, Java and C++ support covariant return types, while C# does not. Adding the covariant return type was one of the first modifications of the C++ language approved by the standards committee in 1998.[4] Scala and D also support covariant return types.
Covariant method argument type
Uniquely among mainstream languages, Eiffel allows the arguments of an overriding method to have a more specific type than the method in the superclass (argument type covariance). Thus, the Eiffel version of the following code would type check, with
putAnimal
overriding the method in the base class:class CatShelter extends AnimalShelter { void putAnimal(Cat animal) { ... } }This is not type safe. By up-casting a
CatShelter
to anAnimalShelter
, one can try to place a dog in a cat shelter. That does not meetCatShelter
argument restrictions. The lack of type safety (known as the "catcall problem" in the Eiffel community) has been a long-standing issue.
The claim that this is unique to Eiffel is questionable since at
least in the current version of Java, code like their example does
compile. Since trygve doesn't currently allow the programmer to do
explicit casting, maybe this is a non-issue. I think I'm now at
least clear on the reverse situation (e.g. your long integer /
short integer example) and why that's OK and allows for
substitutability.
On 09 May 2016, at 05:12, Matthew Browne <mbro...@gmail.com> wrote:Hi Cope,
Thank you for the informative post. I worked on the puzzle have been re-reading your post as well as the Wikipedia article on covariance and contravariance but I am still confused.
class Base {public void bf1() { System.out.println("Base.bf1") }}class Derived extends Base {public void bf1() { System.out.println("Derived.bf1") }public void df1() { System.out.println("Derived.df1") }}class TheBase {public void f(Base b) { b.bf1() }}class TheDerived extends TheBase {public void f(Derived d) { d.bf1() }}{Base aBase = new Base();Derived aDerived = new Derived();TheBase b = new TheBase();TheDerived d = new TheDerived();b.f(aBase);d.f(aDerived)}
class Base {public void bf1() { System.out.println("Base.bf1") }}class Derived extends Base {public void bf1() { System.out.println("Derived.bf1") }public void df1() { System.out.println("Derived.df1") }}class TheBase {public void f(Derived b) { b.bf1(); b.df1() }}class TheDerived extends TheBase {public void f(Base d) { d.bf1() }}{Base aBase = new Base();Derived aDerived = new Derived();TheBase b = new TheBase();TheDerived d = new TheDerived();b.f(aBase);d.f(aDerived)}
Hi Cope,
Thanks for the example. I am trying to reconcile what you are
saying, and what the trygve compiler is doing, with what I am
seeing on Wikipedia and elsewhere on the web, and in my code
experiments. Given your comments about Java, I wasn't surprised to
see that Java's inheritance rules are quite different from
trygve's, but I was a little surprised to see that C++ has pretty
much the opposite behavior as well (at least with respect to
return types).
Consider this trygve code:
class Base {}
class Derived extends Base {}
class Base2 {
public Base func3() { return null }
public Derived func4() { return null }
}
class Derived2 extends Base2 {
public Derived func3() { return null }
public Base func4() { return null }
}
The compiler complains about func3:
line 10: Return type `Base' of `func3()' in class `Base2' must be no less restrictive than `Derived' in the derived class.
I ported this code to C++, which was fine with func3 but complained about func4:
class Base {};
class Derived: public Base {};
class Base2 {
public:
virtual Base* func3() const {return NULL;}
virtual Derived* func4() const {return NULL;}
};
class Derived2: public Base2 {
public:
virtual Derived* func3() const {return NULL;}
virtual Base* func4() const {return NULL;}
};
Error:
return type of
virtual function 'func4' is not covariant with the return type
of the function
it overrides ('Base *' is not derived from 'Derived *')
Do you disagree with the C++ behavior, and if so, why?
In your previous post, you said:
So if a base class method requires a short integer for its first arguments, it is perfectly valid for the derived class to override that method with a method whose first argument is a long integer. The invariants for the derived class arguments can be no less strong than those on the base class arguments: i.e., derived class arguments are more restrictive. One of the tacit arguments is of course self, which naturally follows this pattern. Other arguments follow the rule structure for self. This is called covariance.
If a long integer parameter (in a derived class) is overriding a
short integer parameter (in a base class), wouldn't that be
contravariance, not covariance?
I was also reading this article:
http://cpptruths.blogspot.com/2015/11/covariance-and-contravariance-in-c.html?m=1
It has an example of vehicles and cars, and it says:
The client of VehicleFactory (which has no knowledge of CarFactory) can use VehicleFactory safely even if the create function gets dispatched to CarFactory at run-time. After all, the create function return something that can be treated like a vehicle. No concrete details about Car are necessary for the client to work correctly....so why does the trygve compiler reject func3() in the example above? In the definitions I've seen of the Liskov substitution principle, it should be possible for derived types to be used in place of base types, but I haven't seen any definition asserting that it has to work the other way around.
Thanks,
Matt
On 11 May 2016, at 18:52, Matthew Browne <mbro...@gmail.com> wrote:
Do you disagree with the C++ behavior, and if so, why?
I ran the same code in trygve 2.19 and it now gives a different error message, this time about func4 which is what I would expect:
line 11: Return type `Base' of `func4()' in class `Base2' must be no less restrictive than `Derived' in the base class....but the error message is confusing. For context, here's the code example again:
class Base {}
class Derived extends Base {}
class Base2 {
public Base func3() { return null }
public Derived func4() { return null }
}
class Derived2 extends Base2 {
public Derived func3() { return null }
public Base func4() { return null }
}
Shouldn't the error say, "in class `Derived2'", not "in class
`Base2'"?
I did some exploring of C++ myself today and found that something has changed since I last looked at these issues in detail: namely, that there is absolutely no contravariance support!
On 11 May 2016, at 19:59, Matthew Browne <mbro...@gmail.com> wrote:Shouldn't the error say, "in class `Derived2'", not "in class `Base2’"?
I assume you're talking about contravariant return types, not argument types?I did some exploring of C++ myself today and found that something has changed since I last looked at these issues in detail: namely, that there is absolutely no contravariance support!
The article I mentioned describes a way to achieve contravariant argument types C++, but there seems to be no direct language support for it and perhaps it's a bad idea.
I assume you're talking about contravariant return types, not argument types?I did some exploring of C++ myself today and found that something has changed since I last looked at these issues in detail: namely, that there is absolutely no contravariance support!Yup.
On 11 May 2016, at 21:50, Matthew Browne <mbro...@gmail.com> wrote:Isn't it also now true that trygve doesn't support contravariant return types, as of version 2.19? If it supported both covariant and contravariant return types, then the entire code example in my previous post should compile, should it not?
P.S. I checked a few C++ books on Google Books as well, just to
be sure I wasn't misunderstanding the Wikipedia article and that
Wikipedia wasn't using the term incorrectly.
I think I may have found the reason for the discrepancy...according to this article:
"A return value is contravariant if you can assign the return type to a variable of a less derived type than the formal parameter."
So from the perspective of the calling code, I think this would be an example of a contravariant return type:
class Base2 {
public Base func3() { return null }
}
class Derived2
extends Base2 {
public Derived func3() { return null }
}
Derived2 d = new
Derived2();
Base b = d.func3();
...because b is given the type Base rather than the declared return type of Derived2.func3(), making it contravariant since Base is less specific than Derived.
But when discussing the classes themselves, we can say (I think) that the return type of func4() is contravariant since it's more generic in the derived class, which is also causes a compile error since instances of Derived2 would no longer necessarily be able to substitute for instances of Base2:
class Base2 {
public Derived func4() { return null }
}
class Derived2 extends Base2 {
public Base func4() { return null }
}
Does that sound about right?