Equivalent of classes in LiveCode

LiveCode is the premier environment for creating multi-platform solutions for all major operating systems - Windows, Mac OS X, Linux, the Web, Server environments and Mobile platforms. Brand new to LiveCode? Welcome!

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

aetaylorBUSBnWt
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 118
Joined: Thu Sep 20, 2012 5:11 pm

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Tue Sep 01, 2020 12:47 am

Hi,

I went through Brian's SuperClassCalls code.
And when you get down to it, once I learned about the implied targets and then subsequently invented superFunction/superCommand handlers, there is no way to avoid specifying the class name in the process of making a call up/down the class hierarchy.
Either you have different names at every level, you add the class name to the handler name or you add the complexity of superFunction/superCommand.

So, I agree, Brian's naming convention of className.handlerName is the simplest mechanism to guarantee the right object is being accessed.

I would not generally bother with any handlers such as:

Code: Select all

command reportBehav
   _behaveSee.reportBehav
end reportBehav
as a general rule because as soon as you want to override a handler, class names come into your code.
If you started accessing handlers without the class name, then later subclassed that class, you would probably end up having to rewrite some code to add class names to handler calls, so just always do it.
In fact having that naming convention used on all handlers of a class based object is an explicit signal that the code is leaving the "normal" LiveCode coding conventions.

Script Variable getter/setter handlers don't need a className because they are always going to be unique.

The OOPengine needs some clean up. Need to look at that some more.

Andrew

bwmilby
Posts: 438
Joined: Wed Jun 07, 2017 5:37 am
Location: Henrico, VA
Contact:

Re: Equivalent of classes in LiveCode

Post by bwmilby » Tue Sep 01, 2020 4:02 am

I'm going a little outside my experience, but I think that you would always want to include the base handler name. When anything outside of class code talks to the object, shouldn't it be using the base name and not the class specific version? So when the app wants to tell the object to reportBehav, it would send that message to the object and not worry about where in the class hierarchy it gets handled. If you ever decided to subclass something, then in addition to changing the instantiation you would also need to find every overloaded method that was used and rename it to the new subclass name. Apart from adding/changing parameters, would that be desirable?

The other reason is for handlers that work like the destructor case. If you can use pass, then it is much more efficient (and faster) than even calling the class based handler name. A command that is passed goes directly up the hierarchy. Other commands are going to start with me (unless there is a private match).

If you can use pass, then you don't need to explicitly address which parent class is being overridden. Consider 3 classes: A (root), B (sub), and C (sub sub). If A defines a method and C overrides it, then using either pass or direct access works. If B is later changed to override the method and pass had been used, things would still work as expected with no changes to C. Otherwise, C would also need to be updated to explicitly call B's method instead of A's.

Mark's version of the engine does have some extra commented out code at the moment (mostly from manually integrating some of my ideas). I also have some cleanup to do to integrate his ideas. His is currently more featured than mine since I only create groups and his can use many object types.

Brian
Brian Milby

Script Tracker https://github.com/bwmilby/scriptTracker

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Tue Sep 01, 2020 5:08 am

My thinking is mostly the same as Brian's here.
I mean, I'd love to have namespaces, but we don't.
If you're overloading a function then you'd handle the local stuff and pass the rest on.
That's basically how we have destructors coded.
If you're overriding it, on the other hand, there's no need to pass to the superclass.

Andrew- do you have specific use cases in mind where you need to do what you're describing?
I'm having trouble coming up with a situation where I'd need to call superclass::function.

aetaylorBUSBnWt
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 118
Joined: Thu Sep 20, 2012 5:11 pm

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Tue Sep 01, 2020 2:12 pm

Brian, I can see circumstances where having those "entry" handlers you wrote as being useful and other cases where it is just extra code to write.
I think it would be useful to have them described in the OOPengine stack as an implementation suggestion and how having such handlers helps.
(and also examples of using the entry handlers or not using them)

Some samples of needs to call superClasses in a function override.

Code: Select all

void CTransact::retrieveRecordData(sqlite3_stmt *ppStmt)
{
	setRowID(sqlite3_column_int64(ppStmt, 0));
	
	*ref_idp = (long)sqlite3_column_int64(ppStmt, eTransactRef_id);

	set_TextN(&trnType, (char *)sqlite3_column_text(ppStmt, eTRNTYPE));
....
	setTransactionSign();		//leave debit flag as read from database
	changed = false;
}
is almost the base class.

Code: Select all

void CSplit::retrieveRecordData(sqlite3_stmt *ppStmt)
{
	CTransact::retrieveRecordData(ppStmt);
}

Code: Select all

void CBudget::retrieveRecordData(sqlite3_stmt *ppStmt)
{
	CSplit::retrieveRecordData(ppStmt);
	nextDate = (time_t)sqlite3_column_int64(ppStmt, eBudgetNextDate);
	isActive = (bool)sqlite3_column_int(ppStmt, eBudgetActive);
..........
	changed = false;
}

Code: Select all

void *CChargeFilterTable::getFilter(long inCardID, long inType)
{
	CChargeFilter *aFilter = (CChargeFilter*)CTransFilterTable::getFilter(inCardID, inType);
	if (0 == aFilter) {
		aFilter = new CChargeFilter();
.....
	}
	return aFilter;
}

Code: Select all

- (id) initWithWindowNibName:(NSString*)windowNibName
{
	if (nil == windowNibName) {
		self = [super initWithWindowNibName:@"OcPopUpWindow"];
	}
	else
		self = [super initWithWindowNibName:windowNibName];
	modifyMode = false;
	addMode = NO;
	changedFlag = false;
	sheetMode = NO;			//default is a window, not a sheet
	saveStyleMask = 0;
	return self;
}

Code: Select all

int CWhoTable::deleteRecord(unsigned long aRowID)
{
	getWhatWhoTable()->deleteAllWhatForWho(aRowID);
	int rc = CBaseTable::deleteRecord(aRowID);
	updateDisplayOrder();	//displayOrder needs to be sequential
	return rc;
}
Just a few of the cases of calling upwards in an override.
Yes, typically I call the superclass first or last, but as you see with the last one, the call is in the middle.
Is this something that happens in every method?
No, I actually try to avoid doing it at all, but I won't kill myself to not do it either.

In any case, I believe that based on this whole investigation it has been shown that there is a means to do C++ style coding inside LiveCode with the way that behaviors have been implemented and the additional information that you (Mark, Brian, Bernd) have shared.
Special functions such as messageObject, superFunction, superCommand are not required and in IMO, counterproductive.
The beginning of this thread started with a categorical denial that C++ style coding was possible or even worthwhile.

Moving on.

So far, in order to set up the basic fields and methods for a class there has been a requirement to put buttons or some kind of visual UI element on a Card and then you can open the script editor to write the code.
But under many circumstances, the Classes that I intend to write have no UI at all and never will. They are supporting elements of the program that may be called from different places in the UI, so need to be independent of it.

Is there a means to do declare and write the code for a class and assign inheritance of a behavior without having to place a UI element on a card to start the process?

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Tue Sep 01, 2020 4:11 pm

Is there a means to do declare and write the code for a class and assign inheritance of a behavior without having to place a UI element on a card to start the process?
Yes indeed. Look at the LinkedListClass example I posted. That uses a script-only stack (text file) for the class declaration and no UI elements other than the button that instantiates the list and adds and deletes elements to it.

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Tue Sep 01, 2020 4:50 pm

Thanks for the examples.

Here's how I think I'd deal with this. Obviously YMMV, and there are many ways to get to any result. Mostly working with naming conventions here, but an example of how to get out of your own way without needing to explicitly call superclass functions outside of the inheritance path.

Base class has: void CTransact::retrieveRecordData(sqlite3_stmt *ppStmt)

Assuming CSplit is subclassed from CTransact:
There's no need for CSplit::retrieveRecordData(sqlite3_stmt *ppStmt)... just retrieveRecordData(sqlite3_stmt *ppStmt) since it's inherited.

Same with the call to retrieveRecordData in CBudget... assume it's subclassed from CTransact, and then it becomes a simple inherited function call.


Does CChargeFilterTable::getFilter need to have that name for polymorphic reasons? If not, I'd name it getChargeFilter, and then there's no need to call the base getFilter function explicitly by superclass name, just invoke getFilter.

Same idea with initWithWindowNibName and deleteRecord.
I'd name deleteRecord DeleteWhoRecord and let inheritance do the heavy lifting.
Not sure about initWithWindowNibName, I'd need more context, but you get the idea.

BTW... I love the deleteAllWhatForWho function name. Gonna have to find some place to use that myself.

aetaylorBUSBnWt
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 118
Joined: Thu Sep 20, 2012 5:11 pm

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Tue Sep 01, 2020 9:55 pm

The class hierarchy in my code is:
CBaseRow
CTransact
CSplit
CBudget
Base class has: void CTransact::retrieveRecordData(sqlite3_stmt *ppStmt)

Assuming CSplit is subclassed from CTransact:
There's no need for CSplit::retrieveRecordData(sqlite3_stmt *ppStmt)... just retrieveRecordData(sqlite3_stmt *ppStmt) since it's inherited.

Same with the call to retrieveRecordData in CBudget... assume it's subclassed from CTransact, and then it becomes a simple inherited function call.
As far as the above, once again, implied targeting bites!

I added a call to a handler like Mark suggests to the middle class in the class hierarchy. see below...

Code: Select all

private function _behavCL1.reportBehav p2ndParam, p3rdParam
-- adding the call Mark suggested
reportBehav p2ndParam, p3rdParam
end _behavCL1.reportBehav
And where does the above call to reportBehav go?
Well, remember this code is specifically designed to work with the instantiated object! What is the class of the instantiated object?
behaveSee!!
So the above call goes from the superClass behavCL1 DOWN to its subclass behaveSee and executes the reportBehav handler down there!
Which proceeds to call behavCL1.reportBehav, which calls the behaveSee reportBehav,... Infinite Loop!
Now that is just bad news in any language.

So no, if every class has a method of a particular name or more than one class in a hierarchy has a method with the same name, you really need to specify the call with className.methodName. It might be excessive, but it is definitely not going to be misunderstood.

As far as some of the others, well by setting up a protocol where the naming convention is className.methodName, it provides the means for libraries where you don't control the source code and you want to subclass something, the naming convention provides better assurance that the right handler will be called.

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Tue Sep 01, 2020 11:57 pm

I added a call to a handler like Mark suggests to the middle class in the class hierarchy. see below...
Andrew- sorry, I no longer have any idea what you're talking about.
Where did this code come from? Is that part of the CTransact class?
I don't think I suggested anything like that. Whatever it is.

bwmilby
Posts: 438
Joined: Wed Jun 07, 2017 5:37 am
Location: Henrico, VA
Contact:

Re: Equivalent of classes in LiveCode

Post by bwmilby » Wed Sep 02, 2020 4:59 am

Andrew,

You can't mix passing and direct calls on the same handler. Also, you never can pass up the chain by name unless you use the class version. What Mark was referring to is more like the following:

Class A with subclass B with subclass C. A defines someFunction. B also defines someFunction but ends with a pass. C does not define it at all. When you have an object of class C, then you dispatch someFunction to the object and it will go to B and then A.

Also, consider A defines anotherFunction and C defines anotherFunction that ends with pass. If you dispatch anotherFunction to that C object, it will go to C and then to A.

Lastly, consider A defines aThirdFunction and neither B nor C define it. If you dispatch aThirdFunction to that C object, it goes directly to A. There is no need to define it in B and C just to pass up the chain.

Here's how you would write the handler to work with both calling methods but normally ends with a pass. Note that once you start using the className version, then everything above in the hierarchy will also need to do so.

Code: Select all

command reportBehav pParam1, pParam2
  _className.reportBehav pParam1, pParam2
  pass reportBehav
end command reportBehav

command className.reportBehav pParam1, pParam2
  _className.reportBehav pParam1, pParam2
  dispatch "superClassName.reportBehav" to me with pParam1, pParam2
end className.reportBehav

private command _className.reportBehav pParam1, pParam2
  # do stuff
end _className.reportBehav
If you just want to facilitate the ability of subclasses to override a handler that the current class does not, then you could just provide:

Code: Select all

command className.reportBehav pParam1, pParam2
  dispatch "superClassName.reportBehav" to me with pParam1, pParam2
end className.reportBehav
There is no need to define the handler itself just to pass, the engine message path will take care of that by itself. You could include it and comment it out if you wanted it to be obvious in the code why it was not needed. From a performance perspective, it would be better to dispatch to the superSuperClassName.methodName than to use the pass through version.
Brian Milby

Script Tracker https://github.com/bwmilby/scriptTracker

aetaylorBUSBnWt
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 118
Joined: Thu Sep 20, 2012 5:11 pm

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Wed Sep 02, 2020 6:14 pm

Mark,
I added a call to a handler like Mark suggests to the middle class in the class hierarchy. see below...
Andrew- sorry, I no longer have any idea what you're talking about.
Where did this code come from? Is that part of the CTransact class?
I don't think I suggested anything like that. Whatever it is.
Seems like I misunderstood you then.
I thought you asked: "When I would call a superclass method from within a subclass's method of the same name?"
I replied with C++ code snippets where I did just that.

Your reply seemed to suggest that I did not need to specify the class name to call the superClass's implementation of the method, when that method exists at the class level that the code is executing in.
In C++ it is definitely necessary, so I took your suggestion to apply to Brian's rewrite of the LiveCode SuperClassCalls stack that I posted.
So I tried it. As I posted, it did not work, which is what I expected. I would have been confused if it had.

Brian,

Good explanation of "passing", thanks.

It is my expectation that when implementing the equivalent of C++ classes and C++ coding style, I will primarily follow that kind of protocol in that code. So I would be careful using "pass" in that context, since unlike C++, pass does not return to the calling handler and the other issues you noted.

When outside of that code, I will focus on using "LiveCode style" coding styles.

I still believe that this forum thread has made it very clear that a "class style" code architecture is vey doable in LiveCode, but as I said above, I would be careful to keep the coding styles segregated so that expectations of implied behaviors are also segregated.

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Wed Sep 02, 2020 7:05 pm

Your reply seemed to suggest that I did not need to specify the class name to call the superClass's implementation of the method, when that method exists at the class level that the code is executing in.
Ah. Sorry - I definitely did *not* mean to imply that.
I took your suggestion
Ack!
to apply to Brian's rewrite of the LiveCode SuperClassCalls stack that I posted.
I'll dig my way back through this thread and look for that stack.
So I would be careful using "pass" in that context, since unlike C++, pass does not return to the calling handler and the other issues you noted.
Yes, "pass" is a "we're done here, let's see if anyone else wants a piece of this" command.
I still believe that this forum thread has made it very clear that a "class style" code architecture is vey doable in LiveCode
:D 8)

aetaylorBUSBnWt
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 118
Joined: Thu Sep 20, 2012 5:11 pm

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Thu Sep 03, 2020 6:17 pm

Hi Guys,

Now a meeting of the minds, a single updated OOPEngine?

I compared the OOPEngine version 4 (internal version 1.1.0) from Mark with the OOPEngine from Brian that I received embedded in the SuperClassCalls stack (thinking that is Brian's latest version since it also has an internal version 1.1.0) and found the following differences:
Using BBEdit on text files from each, Going from the top of the files down:

Mark has the following additional functions included:

function superClass
private function classExists? pClassName
(has unused tClassID)
function classIDFromName pClassName


in command registerClasses pCard, pUpdateClasses
local tName, tLongID

Mark calls command registerClass pClassObject, pUpdateClass

whereas Brian appears to reiterate the content of the registerClass command in his registerClasses command.
(what is the difference when between "buttons of pCard" and "buttons on pCard" ?, if any?)

Is there a reason to reiterate the registerClass code instead of just calling registerClass?
local tName, tLongID are unused if you call registerClass


Additional Mark function:
function isAClass? pName


function newClass pClassName, pSuperClass

local tClassID is unused

The newClass function appears to implement the same functionality in slightly different ways that don't seem significant to me, a beginner.
Mark calls his function classExists?, Brian does the test directly (more efficient?).
I would suggest that "control" be used in the statements rather than "button" since classes can now be more than just a button.
After that, there are differences, I am just not qualified to know which is more efficient, if at all.


command deleteClass pClassName

Coming from C++, there is no concept of "deleting a class", you can't.
Now LiveCode preserves everything created or changed including data as a result of testing the program when you try to close stacks (which drives me nuts).
So is this the point of having a deleteClass command - to clean up and get back to a "pure" data set?

After that, once again Mark calls his utility function, Brian makes tests directly.
Finally, ReferenceCount - this functionality is not really implemented completely.
This ReferenceCount has to do with objects created from a class?
If so, then if there are any objects around referencing a class, then deleting it is a really bad thing.
What to do here?


function newObject pClass, pName, pType
vs
function newObject pClass, pName
Two way different implementations.
Mark allows anything to be a class object, Brian only allows groups.
Mark provides for parameters to the Constructor.
I don't believe C++ would allow such, I don't believe I use them even if C++ does.
Where are we going here?

And finally, on newObject - shouldn't this be where the ReferenceCount on the Class of the object should incremented?


on deleteObject pObject
Mark has a comment about NOT deleting the owner object.
I would say definitely do NOT delete the owner object, since I think in this case he is referring to the associated "behavior" object which is the Class. Do we even want to refer to it as the "owner"? The "owner" to me would be the object that contains pObject.

Here we only have one difference:
if exists(control pObject) then
vs
if exists(group pObject) then

Seems like using "control" is more generalized, therefore more appropriate?
And finally, on deleteObject - shouldn't this be where the ReferenceCount on the Class of the object should decremented?


Brian included:

# example class destructor
command destructor

We should have this.


function isObject? pObject
same except Mark has an unused declaration for tClassID. That is the case in a few instances, unused variables that should be removed.


Brian added:
# set the class of an existing control
on setClassForControl pControlID, pClassLongID

Needs to increment the ReferenceCount of the Class object.
In fact, shouldn't this be interrogating if the Class exists in the Class list and either throwing or adding the Class to the Class list?

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Thu Sep 03, 2020 7:08 pm

Wow - there's a lot to work through there.

C++ constructors do indeed take arguments.
That was my model for adding them here.

I think you'll find the latest versions in the github repository. I'd trust that more than what's posted here.
But a few things:

registerClasses/registerClass: Brian and I talked about this... I simply took some redundant code and repurposed the registerClass command to leverage what was already there and in the interest of having a single point of failure for debugging forensics. No real change to the way it operates.

I added newClass and deleteClass for completeness, but I don't see any need to actually use them, so they can easily be removed.

I believe you're correct about referenceCount more properly being in newObject/deleteObject. I'll fix that.

in deleteObject, my comment about not deleting the owner is because Hakan's original did that, and that raised a red flag for me.

I'm fairly sure I got rid of setClassForControl. If I didn't, it should be deleted. I don't see any need for it, and it's certainly not anything I'd try doing, or even consider possible, in C++.

...and yes, there are unused variables which we can remove.

bwmilby
Posts: 438
Joined: Wed Jun 07, 2017 5:37 am
Location: Henrico, VA
Contact:

Re: Equivalent of classes in LiveCode

Post by bwmilby » Thu Sep 03, 2020 8:11 pm

My latest code will always be here:
https://github.com/bwmilby/OOPEngine

Mine is in the master branch and Mark's will be in the mark branch.

My code has been updated a little since what I posted earlier here. I've merged in the idea of creating different types of objects. I've also pulled in the registerClasses change. Based on what you said, I think it is fine to leave a single "pParams" there for initialization and not extend it to the messageObject method.

What you are seeing though is a good example of open source. Mark and I are both headed in the same general direction but are still a little bit apart. I'm taking ideas from his implementation and he is taking some from mine. I do think that the ultimate goal would be to have a common implementation, but I also think that we should keep the two lines for a little bit. We can each implement the same thing slightly differently and then discuss the difference to figure out which will be the long term version. See my version of newObject and compare to Mark's as one example.

Where it makes sense, I do think the direct reference to sClassA[tClass] is cleaner (where you are not needing to throw an error for the object not existing). If we really want to wrap it in a function, then there should be a private version of the function that is used internally. Otherwise, any call to that handler would need to go through the entire message path.

The delete parent thing is due to the way the library was originally implemented. Each object was a group and each group only had a single behavior so they had to nest. So if you had Classes A, B, and C then when you created an object of class C you would have Cgroup inside Bgroup inside Agroup. When you deleted that object, you need to delete all 3 (Cgroup, Bgroup, and Agroup). With nested behaviors this is no longer necessary.

I'll make an attempt to get in there today and pull most of the changes from Mark's code into my branch so there are fewer differences between them. One thing that you can do is compare the branches on GitHub which makes seeing what is different a little easier.
https://github.com/bwmilby/OOPEngine/compare/mark

I also want to pull in the Particles demo changes that Mark made. There are some binary updates to the UI that need to be made so that it appears correctly on my Mac (some of the object are too narrow for example). I already made them in my version. I'm thinking that I want to start from the original version and pull everything into it (making all of the binary changes manually).
Brian Milby

Script Tracker https://github.com/bwmilby/scriptTracker

mwieder
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 3581
Joined: Mon Jan 22, 2007 7:36 am
Location: Berkeley, CA, US
Contact:

Re: Equivalent of classes in LiveCode

Post by mwieder » Thu Sep 03, 2020 8:19 pm

I just updated my github repo fork and with Andrew's latest suggestions and submitted the PR to Brian's repo.

In the process of updating things, I think the reference counts are not necessary, but I left them in as commented code.
The same for newClass and deleteClass.

Post Reply

Return to “Getting Started with LiveCode - Experienced Developers”