Dilemma" #1

This forum is meant for examples of X# code.

Post Reply
FFF
Posts: 1522
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

Dilemma" #1

Post by FFF »

Not sure, where to post, so try it here ;)
Read with interest R's blog re Dilemma. Decided to play around, and wrote:
FUNCTION Start() AS VOID
LOCAL o AS parent
o:=child{}
? o:p
? o:c
RETURN
CLASS parent
EXPORT p AS STRING
CONSTRUCTOR()
p:= "PA"
RETURN
END CLASS

CLASS child INHERIT parent
EXPORT c AS STRING
CONSTRUCTOR()
c:= "Child"
END CLASS

What causes me confusion is:
VN complains, that "c" is no member of parent - true.
But why
a) is then o := Child{} accepted at all
b) one is lead to believe, that o is of class Child - but indeed the compiler makes a Parent instance - which may & will lead to any sort of head-banging, as e.g. calling properties will fail seamingly at will, ...

Sorry, if all of this is clear to others - to me it's not.
Regards
Karl
(on Win8.1/64, Xide32 2.19, X#2.19.0.2.)
User avatar
Chris
Posts: 4562
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Dilemma" #1

Post by Chris »

Hi Karl,

Here's a more self descriptive sample:

CLASS Animal
VIRTUAL METHOD Speak() AS VOID
? "..."
END CLASS

CLASS Cat INHERIT Animal
VIRTUAL METHOD Speak() AS VOID
? "Meeeow"
END CLASS

Let's say you have many classes inheriting from Animal and you want to create a generic function for calling a method on them. Obviously you wouldn't create an overload for every possible inherited class, instead you would write something like:

PROCEDURE MakeAnimalSpeak(oAnimal AS Animal)
oAnimal:Speak()

and you would call it in a way similar to this:

FUNCTION Start() AS VOID
LOCAL oCat AS Cat
oCat := Cat{}
MakeAnimalSpeak(oCat)

This is exactly the same scenario as the sample in your post. It does make sense for the oAnimal param (or it could be a LOCAL) to hold an instance of a subclass, doesn't it? Actually, in this case it wouldn't make any sense to create an instance of the base Animal class itself, so it should probably be marked as ABSTRACT.

If you compile and run the sample above (with any compiler that accepts this syntax :-), you will get the "expected" result. But if you remove the VIRTUAL keywords, then for the code oAnimal:Speak() the compiler will generate code that calls directly the Speak() method of the base Animal class. This happens even though oAnimal in fact holds an instance of the Cat class (which has its own Speak() method), but the compiler doesn't know this at compile time. Making the methods virtual, instructs the compiler to emit different code for the call, which gets resolved at runtime, in this case to the Speak() method of the instance's type (Cat).

Hope this makes some sense!

Chris

ps. a better way to implement the above would be to use an interface, but that's another story!
Chris Pyrgas

XSharp Development Team test
chris(at)xsharp.eu
FFF
Posts: 1522
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

Dilemma" #1

Post by FFF »

Chris,
thx for responding.
Your sample declares and instantiates "Cat" - mine declares Parent, but the code instantiates Child, while in reality the compiler makes a Parent-Instance, hence the complain for the o:c call.
Regards
Karl
(on Win8.1/64, Xide32 2.19, X#2.19.0.2.)
User avatar
Chris
Posts: 4562
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Dilemma" #1

Post by Chris »

Hi Karl,

>>>
Your sample declares and instantiates "Cat" - mine declares Parent, but the code instantiates Child, while in reality the compiler makes a Parent-Instance, hence the complain for the o:c call.
>>>

That was just for making the code more self-describable, but still in the MakeAnimalSpeak() function the parameter is defined as parent, while actually a child is being passed to it. You can just change the local declaration in Start() to "LOCAL oCat AS Animal", that would have the same result.

Chris
Chris Pyrgas

XSharp Development Team test
chris(at)xsharp.eu
User avatar
Otto
Posts: 174
Joined: Wed Sep 30, 2015 6:22 pm

Dilemma" #1

Post by Otto »

If you translate the sample to C# you get the same result: the constructor of the parent AND of the child are both called. Adding a Writeline to the console in the constructor makes that clear.
FFF
Posts: 1522
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

Dilemma" #1

Post by FFF »

Chris wrote:Hi Karl,

>>>
Your sample declares and instantiates "Cat" - mine declares Parent, but the code instantiates Child, while in reality the compiler makes a Parent-Instance, hence the complain for the o:c call.
>>>

That was just for making the code more self-describable, but still in the MakeAnimalSpeak() function the parameter is defined as parent, while actually a child is being passed to it. You can just change the local declaration in Start() to "LOCAL oCat AS Animal", that would have the same result.

Chris
Looks like i can't convey where my pain is ;) - no problem, if a method accepts a child when it expects a parent - that's what inheritance is there for.

What i don't want is:
writing o := Child{}
- and getting NOT a Child, but a Parent instance.
Regards
Karl
(on Win8.1/64, Xide32 2.19, X#2.19.0.2.)
User avatar
Otto
Posts: 174
Joined: Wed Sep 30, 2015 6:22 pm

Dilemma" #1

Post by Otto »

FFF wrote: What i don't want is:
writing o := Child{}
- and getting NOT a Child, but a Parent instance.
You get the child object at runtime, only the compiler can't verify that at compile time, so o:c isn't valid for the compiler.
FFF
Posts: 1522
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

Dilemma" #1

Post by FFF »

Otto wrote:
FFF wrote: What i don't want is:
writing o := Child{}
- and getting NOT a Child, but a Parent instance.
You get the child object at runtime, only the compiler can't verify that at compile time, so o:c isn't valid for the compiler.
Now you lost me ;)
What is unsolvable in a line like o := Child{} ?
I happily admit that i know nothing about compiler internals, but that's hard to swallow...
Regards
Karl
(on Win8.1/64, Xide32 2.19, X#2.19.0.2.)
User avatar
Otto
Posts: 174
Joined: Wed Sep 30, 2015 6:22 pm

Dilemma" #1

Post by Otto »

FFF wrote:Now you lost me ;)
What is unsolvable in a line like o := Child{} ?
the code is:
LOCAL o AS parent
o:=child{}
? o:p
? o:c
The compiler sees o as a parent typed class due to the "LOCAL o as parent" line. This remains the rest of the code the same, regardless the fact that o is instanciated as a child object a line later. by declaring it as a parent, we tell the compiler that whatever lateron may happen to o, it has only to check and assume that o has all the features and characteristics of a parent. That in fact it also has the characteristics of a child (or grand child or whatever) is something the compiler isn't told to check or even isn't allowed to assume.

If we would have typed
var o := child{} (instead of the first two lines)
then o would have been declared as a child for the compiler, because that is implied by the child{} part.

Remember that you could do something like:
LOCAL o AS parent
o:=GetSomeClass("child")
? o:p
? o:c

function GetSomeClass(type as string) as object
local myInstance as object
if type == "child"
myInstance := child{}
else
myInstance := parent{}
endif
return myInstance

The compiler can't now check anymore what kind of class o is other than the commanded "parent" (however, maybe the compiler can due to some ingenius optimalisation process... but I hope you get what I am trying to do here).
User avatar
Chris
Posts: 4562
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

Dilemma" #1

Post by Chris »

Karl,

>>>
What i don't want is:
writing o := Child{}
- and getting NOT a Child, but a Parent instance.
>>>

You do get a Child, calling o:GetType() proves this.

But if you have declared o AS Parent and make a call to a method of the Parent class, named SomeMethod(), then the compiler at compile time emits code that calls exactly that SomeMethod() method of the Parent class (the type of the var, not the type assigned at runtime), no matter if you have defined the same method in the Child class as well. That is, unless you have marked the methods as VIRTUAL, in which case the call is being resolved by the CLR at runtime, and in this case the method of the Child class is being called (the type of the instance at runtime).

In VO, all methods are always being treated as VIRTUAL, so when coding in VO, we don't or didn't take the above into account. I personally don't like the concept of non-VIRTUAL methods by default, but that's how .Net was designed. But, in any case, in both X# and Vulcan you can use the /vo3 compiler option to make this behavior VO-compatible, telling the compiler to treat methods as VIRTUAL, even if they are not marked as such.

That's about runtime implications of the sample code, for the compile time considerations please see Otto's reply.

Chris
Chris Pyrgas

XSharp Development Team test
chris(at)xsharp.eu
Post Reply