Inheritance bug or strange behavior?
BlitzMax Forums/BlitzMax Programming/Inheritance bug or strange behavior?
| ||
The following source produces the output 0 B: 2 C: 3 This is an extremely broken output. z is defined on "a" level, so it should be the same field for all extended as well even if default initialized? But it is able to have 3 different values, what the heck is the compiler doing there? creating dublicates of the same field for every extended class??? Type a Field z% End Type Type b Extends a Field z% = 2 End Type Type c Extends b Field z% = 3 End Type Local me:a = New c handleit(me) End Function handleit(it:a) Print it.z If b(it) Print "B: " + b(it).z If c(it) Print "C: " + c(it).z End Function The following code indeed returns the expected output of 3 B: 3 C: 3 Type a Field z% End Type Type b Extends a Method New() z = 2 End Method End Type Type c Extends b Method New() z = 3 End Method End Type Local me:a = New c handleit(me) End Function handleit(it:a) Print it.z If b(it) Print "B: " + b(it).z If c(it) Print "C: " + c(it).z End Function |
| ||
This won't help, but i find it freaky that a Method New can be made in BMax even thought there is inbuilt NEW! Cool. Edit: See next post. Thanks for that explanation Dreamora; I didn't realize they were the exact same thing. I have looked at the documentation, but I find the tutorials posted on this site much more helpful. |
| ||
method new is exactly what is called when you use new - its the default constructor. See the documentation for further information. |
| ||
This is correct, and is the same in most OO languages. Fields declared in extending types will 'hide' those in base types. |
| ||
so I was partly correct. although I didn't know, that when you upcast to a base you access the base field and that the field can be declared in the base and in extended types. I thing it would be be better to access the base with a super or a self rather than a cast. But That is just my opinion "not an experts' ". Don't bother, I'll just stick to learning. :-) |
| ||
Well you can access the base field with a super, as long as you have cast your object to the extended type. In your original example the Me needed to be cast back to the extended type, then the original field would have been available via super I only disagreed with Dream in that I thought it wasnt an error. I however agree with him that its a stupid thing to do. You are using 8 btyes to store one int. (in a single extention) I agree with Dream totaly, that you should have just the one field, that you allocate either inside "New", or in your create function |
| ||
so what is the difference global int, constant , field just 8 bytes. it was just a general example. I could have other uses for it. Maybe not a great deal of options but usefull none the less. |
| ||
Oh I agree that it could be useful, but in the code you first published it in, it was the wrong tool. In that code, Dreams first post, were the value was allocated to the same field each time in the "New" function was a better way to do it. (And an Int Field is normaly just 4 bytes, all this double name does, is hide an int variable from you. Now be Honest, did you think that you were createing a second variable or just changing the value of the first?) |
| ||
and, from Mark's post Fields declared in extending types will 'hide' those in base types. What would 'hiding' achieve? What could it be used for? They *can* be different variables types (e.g. string, float etc) but I'm not sure how that would help either. |
| ||
It would allow you some great things. But to me its an extremely critical thing. It removes the consistency from inheritance which is something that I thought a "modern language" (which the GC and typesafeness indicates) wouldn't break. At least not unless the language has true support for selection and redefition on extension. Critical because of this little thingy here: Type a Field z% = 3 End Type Type b Extends a Field z% = 2 End Type Type c Extends b End Type Local me:a = New c me.z = 17 handleit(me) End Function handleit(it:a) Print it.z If b(it) Print "B: " + b(it).z If c(it) Print "C: " + c(it).z End Function Now asume how much you can actually break due to that "feature". There is actually no way to tell Class C to use z from Class A. It will always use z from Class B. (yeah I know I can always do A(self).z but thats after all inacceptable if I want to assign c.z statically to a.z as I know that b.z will only be needed if casted to b for some reason! Pointers are no solution as c.z points to b.z so changing c.z pointer will change b.z as well) Result is that you can use Class A anymore for general type declaration for list iteration and the like because the field on the class is totally useless for any extended type because Class B block the whole following inheritance. So until BM learns the needed selection and redifinition mechanics, this "feature" to me is more of a bug than actually a feature as it isn't as controllable and fully implemented as it would be needed to be. But it is a start for something that could turn out to be something really usefull. PS: Mark, might I suggest having a look at Eiffel if you are interested in expanding BM more towards modern OO . It offers an extremely usefull way of how inheritance and selection/undefine/redefine should work in any modern language. As well as how export / access should work as you can define for each field / method / function, which classes are allowed to use it. |
| ||
@H&K, yes I thought I was craeting a second variable, that is what my whole confusion came about. where I thought I knew something I had no real understanding. |
| ||
But to me its an extremely critical thing. It removes the consistency from inheritance which is something that I thought a "modern language Critical? Consistency?Most modern and pragmatic OO languages work just like that. First point : you just can't "override" a field in an inherited type, as a field is just a piece of data in BlitzMax, just like in C# or Java. If member access was unified as in a 100% OO language, then there would actually not even be any concept of simple field, and "field" access would actually imply calling a hidden getter/setter, with the cost of a virtual function dispatch. A truely, purely and consistent OO language is certainly nice, but for some time the speed cost is *not* desirable for some of the things most programmers are using BlitzMax for. Second point, in fact more important: if what you expected here was some kind of overriding, does it actually make sense here? Also I have a simpler question: what *exactly* did you expect BlitzMax to do in your example? Remember that the declaration Field z% = 2Is strictly a shortcut for Field z% Method New() z = 2 End Method If what you expected was to *not* redeclare anyy field but just have another initialization put in the constructor behind the scenes, as it does right now) then OK, that could actually be not that bad. But to me that's another special case, exception to the rule or simply put, inconsistency. |
| ||
It seems to work exactly I would expect it to. Since the field is private for a given type/instance, casting to a specific type would make visible the field at that level. |
| ||
It would allow you some great things. Fantastic! Now, what could it be used for? |
| ||
To have the "core type" hold a given value, but to save a modified version of this value on the extended types for example. So if you use it as core type it shows "untransformed" values but the extended type has transformed values (that are kept updated all the time) Koriolis: What I actually assume is that field z is field z throughout the whole inheritance. It makes actually quite little sense to have a field z, that shows X when you ask for its value on core type, but that shows y if you cast it to an extended type. I fully understand that this allows some interesting things. And am aware that this sadly is needed to work like this if we want to get multi implementation of abstract types at some point, as it would badly break otherwise unless redefine / undefine / select mechanics come in. PS: Check out eiffel and you will see that you don't lose anything with a clear and fully consistent OO language with re / un-define and select mechanisms. You actually get quite a lot when it comes to usefully design your OO system. That is actually a reason why Eiffel is used in critical systems like medical stuff and aircrafts, where Java and C# never will be used and C/C++ only after tousands of hour of testing normally. It isn't one of those funny languages that never grow up due to too complicated ideas. Its quite the opposite, as C# already has taken a few of Eiffels mechanics (delegates for example, which are called agent in Eiffel) as other languages as well. |
| ||
I dont care if it is supposed to be in a modern OO languge or not. My only difference of opinon with Dream is that I didnt thing it was an error to be able to do it. I cannot honestly thou dissagree with Dream that it a stupid thing to do. Cos it is. Its not a useful tool at all. I did admit earlier that possibly it might be useful in some weird code model, but I just think it is a way to confuse you when you are useing the type. If I want a different field in the exteneded type, I would give it a new name, if I wanted the same variable to have a different value in the extended type, I would simply give it a new value. @Jesse, Im really supprised that you did know you where createing a new value. But given that you did know that, can you now see that you need to cast, even if you had given different names? Type a Field z% End Type Type b Extends a Field y% = 2 End Type Type c Extends a Field x% = 3 End Type Local me:a = New c Print C(me).x 'Print me.x |
| ||
Thats correct, you're overriding fields, that's a BMax feature. |
| ||
Well in fact ziggy that's precisely the point: you're NOT overriding anything, you create a new field that hides the one in the parent type. But anyway I would indeed call that a by design feature, surely not a bug. |
| ||
you create a new field that hides the one in the parent type Err, thats what overriding is. (Its just that noramally we'd only do it for functions/Methods) |
| ||
@H&K, Just note that I admitted to have misunderstood the whole concept. I had originally created a field name index for each of the types to identify the type of operation I was doing. 1 was for the addition type, 2 for subtraction type..... it was kind of like a constant. Considering what I just learned from the language, under normal circumstances its not possible but now I know. I am not the kind of person that denies something just to look good. I am more humble than that. I am not afraid to make mistakes just scared that when I point them out somebody crucifies me. its OK I do it anyway. |
| ||
@Jessie. I wasnt having a go, I was just pointing out that if you had used different names in the first place, it would have been more obvious that you had to cast, because the field in "C" wouldnt exist, as apposed to the program using the field in the base type. When you first posted it, I had thought that your understanding was that you were only createing one field. As you have said other, then I believe you. Was just shocked that you understood it to that level, yet did realize that you needed to cast. I do however think its a silly thing to do (duplicate names), as it gains you no memory, just makes it harder to see iif any future problem is one of allocation or casting. |
| ||
Koriolis: Yes it is a feature. but I'm not sure if a language should really have a "non-name uniqueness" features within inheritance of non-abstract types. (I agree that it is needed when you actually implement an Abstract Type, at least *and only then* if Mark is planning to add the possibility to implement multiple abstract types within a single type as C# / Java allow it. But I think this is the only circumstance where a new instance of the name should be created after all, not if you define a field with the same value on an extended type of a non-abstract type) |
| ||
I didn't know about the cast. I learned it from the explanations given by others. |
| ||
If you are going to use fields of the same name, then you have to make sure that the object is cast to the right type. In your example you have cast your type C to an A, so it trys to use the field in A. If you recast it back to a C, or make Me a C in the first place, you should get the value you expect As it was I, who first posted that you needed to cast, Then you could at least imply that I had something to do with it rather than saying "the explanations given by others" (This is not important, but you made me cry) In that thread you had a specific problem that was anwsered (by MEEEEE), this thread tho is a disscustion of if its a valid thing to do. ;) And I dont think it is. I didnt say so in your thread, because it was not my place to say so. However in this thread, you are the only person I know who is acctualy using it, so thats why I keep questioning you. |
| ||
sorry that I wasn't too concerned with who posted what. in the future I'll try to be more in touch with who I read from. In my blured out memories I can kind of recall that post. And yes it was your post. edit: don't miss understand me. your opinion is important to me, It's just that it didn't sink in until a couple of posts down the thread with further explanation. |
| ||
@Dreamora: I understand your point, but I think this is a very important feature, if not, an update of a base class (an update of a module) could cause inherited classes to become incompatible. |
| ||
@Jesse, If you want a laugh at how long it took me to get it then read this http://www.blitzbasic.com/Community/posts.php?topic=60015#669484 |
| ||
ziggy: and how are they not incompatible with dublicate fields? What is the use if the module updates the field but your extended class does not get updated? Thats about pointless as the main use of extending is to have the full functionality of the base class and extend it, not replace it. |
| ||
Good one H&K. thanks. Now I don't feel alone in this. |
| ||
Koriolis: Yes it is a feature. but I'm not sure if a language should really have a "non-name uniqueness" features within inheritance of non-abstract types. Actually I agree here. BlitzMax would probably better generate an error. But IMHO *not* do any kind of overridng here, for speed concerns. Err, thats what overriding is. (Its just that noramally we'd only do it for functions/Methods No, that's not overriding at all. Not in the sense commonly admitted in OO languages (that would be overloading, not overriding). Overriding is a dynamic (runtime) feature ( in general implemented via virtual tables) allowing a method to have multiple implementations (or if you prefer to have several methods to be transparently selected at runtime based on the object's runtime type). Here there is absolutely nothing that imply runtime support. You have two completly different pieces of data, they just happen to have the same name in the parent and the inherited type. The parent and inherited type just form different namespaces here, that's about all. And because they have the same name, from the inherited type perspective the homonym field in the base type is hidden. Everything works at compile time in this area, and the object's runtime type never enter the equation. |