New fbc branch ('inheritance') on sourceforge (fbc SVN)

General discussion for topics related to the FreeBASIC project or its community.
Post Reply
stylin
Posts: 1253
Joined: Nov 06, 2005 5:19

Post by stylin »

Great news, v1ctor. Keep up the excellent work !
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

Are there plans to complete the user-define type support by adding the ability to define static member data (in addition to the static member function)?

Behavior:
- When a member data is declared as static, only one copy of the data is maintained for all instances of the type.
- Static member data can be accessed without using an instance of the corresponding type or from any instance of this type.

Example of proposed future use:

Code: Select all

Type animal
  Declare Constructor ()
'  ..........
  Dim weight As Single
'  ..........
  Static count As Integer
End Type

Constructor animal ()
'  ..........
  animal.count += 1
End Constructor



Print animal.count
Dim dog As animal
Dim cat As animal
Print animal.count, dog.count, cat.count ' the three forms are valid

Sleep
My method presently to simulate static member data:

Code: Select all

Namespace animal
  Type kind
    Declare Constructor ()
    Declare Property count () As Integer
    Declare Property count (Byval value As Integer)
'    ..........
    Dim weight As Single
'    ..........
  End Type
  Dim count As Integer
  Constructor kind ()
'   ..........
    animal.count += 1
  End Constructor
  Property kind.count() As Integer
    Return animal.count
  End Property
  Property kind.count(Byval value As Integer)
    animal.count = value
  End Property
'  ..........
End Namespace




Print animal.count
Dim dog As animal.kind
Dim cat As animal.kind
Print animal.count, dog.count, cat.count ' the three forms are valid

Sleep
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

hello fxm
as a workaround i simulate an instance shared counter with a static function
http://www.freebasic.net/forum/viewtopic.php?t=17558

Joshy
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

D.J.Peters wrote:hello fxm
as a workaround i simulate an instance shared counter with a static function
http://www.freebasic.net/forum/viewtopic.php?t=17558

Joshy
Yes.
Before I have also used this principle to simulate static data within Type ... End Type:
http://www.freebasic.net/forum/viewtopi ... 199#129199

In my last simulation, I use another method with Namespace ... End Namespace to encapsulate a global data, and with property to allow access from any instance.
My specific example above shows its use to manage a counter of objects, but its use may be more general:
My last method simulates a static member data with full access in read and write from namespace-identifier or from any instance of namespace-identifier.type-name.

- Other example to simulate static member data with full access from namespace-identifier (animal) or from any instance of namespace-identifier.type-name (dog, cat):

Code: Select all

Namespace animal
  Type kind
'    ..........
    Dim weight As Single
    Declare Property list () As String
    Declare Property list (Byref newlist As String)
'    ..........
  End Type
  Dim list As String
  Property kind.list() As String
    Return animal.list
  End Property
  Property kind.list(Byref newlist As String)
    animal.list = newlist
  End Property
'  ..........
End Namespace



animal.list = "animals : "
Print animal.list
Dim dog As animal.kind
dog.list = dog.list + "dog "
Dim cat As animal.kind
cat.list = cat.list + "cat "
Print animal.list, dog.list, cat.list ' the three forms are valid

Sleep
- Shorter example to simulate simplified static member data with restricted access from namespace-identifier only (animal):

Code: Select all

Namespace animal
  Type kind
'    ..........
    Dim weight As Single
'    ..........
  End Type
  Dim list As String
'  ..........
End Namespace



animal.list = "animals : "
Print animal.list
Dim dog As animal.kind
animal.list = animal.list + "dog "
Dim cat As animal.kind
animal.list = animal.list + "cat "
Print animal.list

Sleep
yetifoot
Posts: 1710
Joined: Sep 11, 2005 7:08
Location: England
Contact:

Post by yetifoot »

wow, my hero is back submitting code!

Image

(sorry, closest image I could find)
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

fxm wrote:In my last simulation, I use another method with Namespace ... End Namespace to encapsulate a global data, and with property to allow access from any instance.

My last method simulates a static member data with full access in read and write from namespace-identifier or from any instance of namespace-identifier.type-name.

- Other example to simulate static member data with full access from namespace-identifier (animal) or from any instance of namespace-identifier.type-name (dog, cat)
In order to simplify the coding of this same example, 2 macros with parameters may be used:
- one for the static data property declaration block (called for this example with the 2 parameters list, STRING),
- another for the static data body block (called for this example with the 4 parameters animal, kind, list, STRING)

Code: Select all

#MACRO _STATIC_DATA_DECLARATION(_data_name, _data_type) ' to be inserted in the type block (_type_name) inside the namespace block (_namespace_name)
  DECLARE PROPERTY _data_name () AS _data_type
  DECLARE PROPERTY _data_name(BYREF AS _data_type)
#ENDMACRO

#MACRO _STATIC_DATA_BODY(_namespace_name, _type_name, _data_name, _data_type) ' to be inserted below the type block (_type_name) inside the namespace block (_namespace_name)
  DIM _data_name AS _data_type
  PROPERTY _type_name._data_name() AS _data_type
    RETURN _namespace_name._data_name
  END PROPERTY
  PROPERTY _type_name._data_name(BYREF _new_data AS _data_type)
    _namespace_name._data_name = _new_data
  END PROPERTY
#ENDMACRO



NAMESPACE animal
  TYPE kind
'    ..........
    DIM weight AS SINGLE
'    ..........
    _STATIC_DATA_DECLARATION(list, STRING)
    END TYPE
  _STATIC_DATA_BODY(animal, kind, list, STRING)
'  ..........
END NAMESPACE



animal.list = "animals : "
PRINT animal.list
DIM dog AS animal.kind
dog.list = dog.list + "dog "
DIM cat AS animal.kind
cat.list = cat.list + "cat "
PRINT animal.list, dog.list, cat.list ' the three forms are valid

SLEEP
v1ctor
Site Admin
Posts: 3804
Joined: May 27, 2005 8:08
Location: SP / Bra[s]il
Contact:

Post by v1ctor »

Static data members are in the to do list but won't be added atm - at least i can't work on that, sorry.

I added the IS operator, now you can do (using sir_mud's example):

Code: Select all

const DOG_ACTION = "rub belly"
const CAT_ACTION = "sleep"

type Animal extends Object	'' must extend Object so RTTI will be generated for this class
	dim as string name
end type

type Dog extends Animal
	declare constructor (name_ as string)
	declare function getAction as string
end type

constructor Dog (name_ as string)
	base.name = name_
end constructor

function Dog.getAction as string
	return DOG_ACTION
end function

type Cat extends Animal
	declare constructor (name_ as string)
	declare function getAction as string
end type

constructor Cat (name_ as string)
	base.name = name_
end constructor

function Cat.getAction as string
	return CAT_ACTION
end function

function getAction( a as Animal ) as string

	if( a is Dog ) then
		return cast( Dog ptr, @a )->getAction
	elseif( a is Cat ) then
		return cast( Cat ptr, @a )->getAction
	end if
	
end function

sub main
	var pluto = Dog( "Pluto" )
	var tom = Cat( "Tom" )

	assert( getAction( pluto ) = DOG_ACTION )
	assert( getAction( tom ) = CAT_ACTION )
	
	print "all tests ok"
end sub

	main
No virtual methods yet. They may be added much faster now that support for RTTI was implemented.
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

hello v1ctor,
looks good so far
but one question
why you need "dim" as string name now
or is it optional ?

Joshy
agamemnus
Posts: 1842
Joined: Jun 02, 2005 4:48

Post by agamemnus »

That "is" keyword sounds a bit funky (if useful) to me...

I'm a little worried about an operator that checks for what the type of something is. You're not supposed to be able to know what type something is in a statically-typed language.. it adds forced bloat. Then again I could be totally be wrong and the type info could be inevitable in inheritance...

Is the "is" somehow implemented as a compile-time macro? Or...
marcov
Posts: 3462
Joined: Jun 16, 2005 9:45
Location: Netherlands
Contact:

Post by marcov »

agamemnus wrote:That "is" keyword sounds a bit funky (if useful) to me...
IS and AS are standard Delphi operators. AS is usually a safe cast (exceptions is thrown when the cast fails), I don't know how other languages do this, I assume with some variantion on CAST() builtin instead of an operator.
I'm a little worried about an operator that checks for what the type of something is. You're not supposed to be able to know what type something is in a statically-typed language.
That's what inheritance does. In a certain type (TAncestor), in it there can be TAncestor, but also one of the descendants. IS allows to make a difference.

The main use for IS is if you have a base class say two main inheritance tree. (say TOrganism with TPlant and TAnimal as descendant) , that you can make a difference in some general routine that takes an TOrganism. E.g. to set an extra field or so.

This gets even more important when meta classes are supported. (variables that can hold a classtype).
it adds forced bloat.
Hardly, since it is a few bytes per class definition. Not per instance. Some of the information is also used for generalized streaming (it is more or less the identification of a class)
Then again I could be totally be wrong and the type info could be inevitable in inheritance...

Is the "is" somehow implemented as a compile-time macro? Or...
Usually it is a function, since you must walk the inheritance chain with a loop. (you start with the current class, and then walk till you find either the root object or the class that you are checking for)



"x aS TY" is roughly equal to

Code: Select all

 if x is ty then 
   result:=ty(x)   // normal cast
 else
   raise EInvalidCastException; 
agamemnus
Posts: 1842
Joined: Jun 02, 2005 4:48

Post by agamemnus »

I realize the usefulness of "IS"...

marcov wrote: Hardly, since it is a few bytes per class definition. Not per instance. Some of the information is also used for generalized streaming (it is more or less the identification of a class)
Yes, it's a few bytes per class definition, but also per instance... In order for the program to know what type a variable is, it needs to store that information, right? If you have 2^16 types max, that's 2 bytes per type instance. If you have a lot of instances and little data in each instance (like, 3 bytes), that is very significant.
Usually it is a function, since you must walk the inheritance chain with a loop. (you start with the current class, and then walk till you find either the root object or the class that you are checking for)
An inheritance chain makes sense if you could change the inheritance of a UDT on the fly... but, the inheritance scheme is set in stone when you compile. If it was a function, I'd imagine that it would/should use a binary lookup table that is created upon compilation, since that would always theoretically be faster than an inheritance chain.
vdecampo
Posts: 2992
Joined: Aug 07, 2007 23:20
Location: Maryland, USA
Contact:

Post by vdecampo »

2 bytes x 2^16 = 131072 bytes!

So worst case is .013% of 1Gb of RAM. Is that really an issue?

-Vince
marcov
Posts: 3462
Joined: Jun 16, 2005 9:45
Location: Netherlands
Contact:

Post by marcov »

agamemnus wrote:

Yes, it's a few bytes per class definition, but also per instance... In order for the program to know what type a variable is, it needs to store that information, right?
When doing inheritance, any instance has one pointer back to the classtype. Regardless of IS.

This because if you want VMTs (virtual method table, the struct that virtual methods operate on) a pointer from instance back to the type can't be escaped. Any OO language has this.

That's why C++ objects are either auto-initialized or initialized dynamically via NEW. But they are always initialized. Why? That VMT pointer must be set.
If you have 2^16 types max, that's 2 bytes per type instance. If you have a lot of instances and little data in each instance (like, 3 bytes), that is very significant.
First, usually a pointer is used, since it saves the extra lookup when calling a VMT method, so is critical for performance.

Second, nobody builds in 65536 limitations in modern implementations anymore. It is 2011 now, and entry level PCs have 4GB.

Third, keep in mind a dynamic allocation will never be "just" 3 bytes. There is heapmanager administration overhead, minimal allocation size etc. Any dynamic allocation is probably at least 16 byte, probably more.

So any such scheme will be array based. Many arrays even, to even feel the memory impact, since extremely large arrays are often not possible. (you often can't allocate the available memory in one allocation in modern OSes)

So _if_ sb actually cares about this scenario, you need two object types, one with and one without VMTs.

Delphi solves this by separating "records" (structs/UDTs) from classes.

Records are just structs/UDTs and don't have the pointer to the VMT/classtype. Classes do (and in Delphi's case, are always dynamic)

More than memory savings, the existance of the non VMT record types are also useful to add behaviour to e.g. OS structs and other UDTs that can't be assumed to be under the language's control. (and thus can't be assumed to have a VMT pointer)

In the past, Delphi's precursor experimented with combining both aspects in a type (close to struct/UDT) that had a VMT depending on the fact if it had virtual methods, but that turned out to be a dead end. It let to complicated code on the compiler side and confusing behaviour for new beginners. When the object type got more complicate this was untenable afaik.
Usually it is a function, since you must walk the inheritance chain with a loop. (you start with the current class, and then walk till you find either the root object or the class that you are checking for)
An inheritance chain makes sense if you could change the inheritance of a UDT on the fly... but, the inheritance scheme is set in stone when you compile.
True. But the code-paths not, so due to differing codepath, the same procedure can be passed differing types to the same parameter.

More importantly that is the whole point, that base code can shuffle around objects without knowing which exact descendant is in it.

Knowing the whole codepath, though doable for isolated cases is equal to the halting problem, and not solvable for non trivial cases.
If it was a function, I'd imagine that it would/should use a binary lookup table that is created upon compilation, since that would always theoretically be faster than an inheritance chain.
Many binary trees then, since there are two arguments typeof(x) IS TY, it is not a simple one dimensional arra lookup, since both type of X and TY vary.

Since inheritance trees are usually not deeper than 10-20 (and usually a lot less), this is not that important, performancewise. Keep in mind that many more complicated algorithms need more setup that might not be worthwhile with low N.
v1ctor
Site Admin
Posts: 3804
Joined: May 27, 2005 8:08
Location: SP / Bra[s]il
Contact:

Post by v1ctor »

marcov explained it well. There's no bloat. If your class has virtual methods, there's no way to call the right method without including a hidden pointer to the virtual methods table. RTTI comes for free then, because that pointer is reused to locate the RTTI table.

If there's no virtual methods, you can force RTTI by extending the internal OBJECT class.

UDT's that don't have any of those (virtual methods or Object extension) will still as before, with no RTTI or hidden pointer.
agamemnus
Posts: 1842
Joined: Jun 02, 2005 4:48

Post by agamemnus »

I see, I see.
UDT's that don't have any of those (virtual methods or Object extension) will still as before, with no RTTI or hidden pointer.
Ah, ok. Great. Just what I wanted to hear.


marcov wrote:
Agamemnus wrote: If it was a function, I'd imagine that it would/should use a binary lookup table that is created upon compilation, since that would always theoretically be faster than an inheritance chain.
Many binary trees then, since there are two arguments typeof(x) IS TY, it is not a simple one dimensional arra lookup, since both type of X and TY vary.
I don't see how it'd be many binary trees. Just one tree of parents per UDT... unless... you think IS would apply to grand-children, etc.? (maybe there needs to be one "IS" for children (ie: exact matches) and one for everyone else?)
Post Reply