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: Klaus, FourthWorld, heatherlaine, kevinmiller, robinmiller

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

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Tue Aug 25, 2020 12:38 am

What you are listing are strictly for calling within the current class. You can't go up the hierarchy with them.

My additions are for calling the super class command/functions.
Normally you would only use them from within the subclass, but that is not a requirement.

You could just skip them and use

dispatch "commandName" to the behavior of objectID with objectID

and

dispatch function "functionName" to the behavior of objectID with objectID
and

dispatch "commandName" to the behavior of objectID with objectID, param1, param2, ...

and

dispatch function "functionName" to the behavior of objectID with objectID, param1, param2, ...

My functions are just shortcuts, at least they are shortcuts when you only have one or two parameters, after that, they are more work because you have to parse the parameters and put them back where they belong.

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

Re: Equivalent of classes in LiveCode

Post by bwmilby » Tue Aug 25, 2020 1:02 am

You cannot call a function/command up the hierarchy with the same name and have control return. Passing the target only lets the abstract class code know which object it is dealing with, it does not allow access to the private variables that are instantiated for the object.

You can use “pass” to allow the next object handle the request (possibly doing some work before, but I don’t think parameters can be adjusted). This also works if every level doesn’t override the handler.

If you absolutely need to call and return, then you need to look at my earlier constructor code. It requires special code in every level except the top/base class. My first version does not handle skipped levels. It would be much more efficient to use a model where you created class specific names for each handler that needed this type of access.
Brian Milby

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

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

Re: Equivalent of classes in LiveCode

Post by mwieder » Tue Aug 25, 2020 1:05 am

What you are listing are strictly for calling within the current class. You can't go up the hierarchy with them.

My additions are for calling the super class command/functions.
Normally you would only use them from within the subclass, but that is not a requirement.
No, unless I'm being dense again, why not

Code: Select all

dispatch "commandName" to objectID
# and
dispatch function "functionName" to objectID
# and
dispatch "commandName" to objectID with param1, param2, ...
That's more in line with the way C++ classes work. You shouldn't need to invoke the inheritance path explicitly to access class methods.

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

Re: Equivalent of classes in LiveCode

Post by bwmilby » Tue Aug 25, 2020 3:02 am

I've pushed the update that allows script only stacks to be registered as classes:
https://github.com/bwmilby/OOPEngine/co ... 0090c016ab

The link is to the commit so you can see the actual code changes that we made.

Here is the most relevant handler addition:

Code: Select all

command registerClass pClassObject, pUpdateClass
   local tName, tLongID
   if not exists(pClassObject) then
      throw "class object" && pClassObject && "not found"
   end if
   put the short name of pClassObject into tName
   put the long id of pClassObject into tLongID
   if sClassA[tName] is not empty and pUpdateClass is not true \
         and sClassA[tName] is not tLongID then
      throw "class" && tName && "already exists in index"
   end if
   put tLongID into sClassA[tName]
end registerClass
Brian Milby

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

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

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Wed Aug 26, 2020 12:26 am

Hello,

Regarding the comments about my superFunction and superCommand code.

I had tested what I posted, unfortunately with the changes to OOPengine going on and my changes to it, I lost several critical items.

I am almost done recreating and will post details regarding what I changed and how and why it works.

It works much much closer to C++ than you believe.

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

Re: Equivalent of classes in LiveCode

Post by bwmilby » Thu Aug 27, 2020 4:36 am

Here is a demo that I was working on to show the difference between dispatching to an object and the behavior of the object.
ClassDemo.png
Screen Shot
(I manually added the bold to indicate the button clicked)
Read the log from the bottom to the top (so Blue was the first button clicked)

Here is the code for the vehicle class:

Code: Select all

local sColor

function getColor
   return sColor
end getColor

command setColor pColor
   put pColor into sColor
end setColor

on mouseUp
   local tBehavior
   put the behavior of me into tBehavior
   if tBehavior is not empty then
      dispatch function "getColor" to the behavior of me
      put the result into tColor1
      put the short name of tBehavior into tBehavior
   end if
   dispatch function "getColor" to me
   put the result into tColor2
   put tBehavior && tColor1 &&\
         the short name of me && tColor2 & lf before field "log"
end mouseUp
In all cases, the color is actually stored in the private variable of the vehicle class. If you dispatch to the behavior, you do not get the value that is attached to the object, but the one attached to the behavior. My main point is that if you dispatch to the parent/super/behavior, then any variables that need to be scoped per object will need to be manually maintained based on the target value added as another parameter. Also as you pointed out, you must always dispatch to the class definition object which maintains state manually since the normal message path won't contain the correct data.
Attachments
ClassDemo.livecode.zip
ClassDemo stack
(2.6 KiB) Downloaded 36 times
Brian Milby

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

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

Re: Equivalent of classes in LiveCode

Post by mwieder » Thu Aug 27, 2020 4:59 pm

<sigh> Classes don't exist. In C++ you can't invoke methods in a class, only in an instance of a class after an object has been instantiated.
So if we're emulating C++ classes here, you shouldn't (and shouldn't be able to) dispatch messages to a class/behavior. The only way you should be talking to a behavior object used as a class is through an object that inherits from the behavior chain.

On the other hand, C++ classes can have static variables which are same for all subclasses (i.e., set the vehicle color to gray and all the vehicles turn gray), and that's a bit harder to emulate in LiveCode, and there you might have a case for talking directly to the behavior object.

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

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Thu Aug 27, 2020 11:43 pm

It has been several days since I was on the forum because I was working on documenting what I was seeing which was different from what earlier comments were stating. I wanted to collect it all together and also provide code to demonstrate what I was seeing.

The following are the results of my modifications to the addressingBehaviorsChangesTarget.livecode stack originally created by Bernd on August 12, 2020 and the various modifications of OOPEngine by Mark Wieder and Brian Milby.

Some of what is below is obvious to most of you, but I am being overly descriptive to be certain you know what I did vs assuming I did something, because I may not have, especially since I am new to LiveCode. C++ I know well.
I have tried to provide direct data from the results of the tests that I ran with the attached code, rather than interpretations.

I created a Classes card with the following buttons for my classes:

behavCL2 - base class -- meaning there is no assigned behavior
behavCL1 - subclass of behavCL2 -- meaning its behavior is behavCL2
behaveSee - subclass of behavCL1 -- meaning its behavior is behavCL1

I added at least one script variable for each of the classes along with functions to get and set their values.
Each script variable is initialized to a value in its declaration.

On the main card I have the same UI elements, but I took over the baseButtonDispatch scripts.

The baseButtonDispatch button now has a script variable called sDoIt that contains the instantiated object.

If this were C++, then I would have:

behaveSee *sDoIt; //sDoIt is a pointer to an object whose Class is behaveSee.

One of the attributes that LiveCode has for objects that have a hierarchy of behaviors is that those objects also have an associated data set of script variables, segregated into sections that represent each superClass.
This data set is private to that object and unique to that object AND COMPLETELY SEPARATE from the LiveCode objects that represent the classes.

sDoIt has no script variables and no script code itself. This is important to remember.
When sDoIt is the LiveCode Target, sDoIt's copy of script variables are active, but all the code executed on its behalf belongs to the class objects which will access the sDoIt copy of script variables ONLY as long as sDoIt is the Target.

Going up the class hierarchy to a particular class/superClass command/function involves using "to the behavior of someObject" in the dispatch call. "to the behavior of someObject" changes the Target to that "someObject".
This is why having an objectID parameter is important, it provides access to the data set you actually want to work on.


(sorry to harp on the above so much, but to a C++ programmer the above distinctions are not obvious)

So to put names on things: I create an sDoIt object from the behaveSee Class. I gave it the name sDoIt.

Code: Select all

   put newObject("behaveSee", "sDoIt") into sDoIt
The sDoIt object has its own copy of behaveSee script variables, behavCL1 script variables and behavCL2 script variables.

If you make a call to any of the classes themselves without referencing the sDoIt objectID, then you are going to be operating on either the class's own script variables or something else. It depends upon how you made the call.

So I modified the OOPengine's newObject function so that instead of calling a method named constructor when initializing the new object that has been created, the methods are named the same as the class name. (At one point Mark had this in his OOPengine)

In addition, the constructor methods receive the ObjectID of the new object that has just been created.

Code: Select all

# snippet from invokeConstructorsOf pObject command
# call the class constructors in order
   repeat for each line tClass in tClassList
      --dispatch "constructor" to tClass      --initializing with class not instantiated object
      dispatch tClass to pObject with pObject  --calls super class's constructor with instantiated object.
      -- and last iteration through repeat loop calls the constructor of the class of the instantiated object.
   end repeat
While it is NOT an object related to a class anymore, I did put a named constructor into the baseButtonDispatch script.
That command is called from the mouseUp handler of setBehaviors.
The sDoIt object is created then.

Clicking on the baseButtonDispatch button (no shift key, no control key) causes the following code to execute:

Code: Select all

if sDoIt is not empty then
      dispatch "reportBehav" to sDoIt with sDoIt
      deleteObject sDoIt
      put empty into sDoIt
   else
      put "" into field 1
   end if
Remember, sDoIt's class is behaveSee, so it has a private copy of all script variables for that class hierarchy and can access all of the commands/functions of that class hierarchy. That dispatch command starts the whole process off.

Key things about that dispatch command:
-"to sDoIt" sends the command to the sDoIt object, which means behaveSee class handlers - there is no sDoIt code.
- "with sDoIt" explicitly sends the whole unique data set assigned to the sDoIt object to the reportBehav handler.

Other KEY THINGS:
In the dispatch above: -"to sDoIt" means that sDoIt is the "Target" during this execution of reportBehav in the behaveSee class.

IF you attempted to get to the same code: reportBehav in the behaveSee class, but used the superCommand command, things would be radically different. Since there is no reportBehav code in the sDoIt button script, you might think you are going to the same place, BUT...
superCommand uses "to the behavior of sDoIt" to specify the destination, which is the behaveSee Class Object.
So now the "Target" is the behaveSee Class object, NOT the sDoIt object.
You got to the same code, but the Target changed to that of the "Class".

So IF at any time you are going to need to access a command/function via a "to the behavior of" means, then that command/function MUST have an ObjectID parameter and it MUST use it to access everything because the "Target" will be the "Class Object" not the instantiated data object that was created via the newObject() function call.


So now we are in behaveSee's reportBehav handler.
At this point, the target is the button object sDoIt, named sDoIt. The target's behavior is behaveSee.
These are the same as the values from the "pid" parameter to the reportBehav handler.
In this handler, "this me" is the behaveSee class. The "this me" behavior is behavCL1, which is its superClass.

Now we want to go to a method of the superClass: behavCL1. In this case I chose to call a function. I encapsulated that call in a function contained in the main card stack: superFunction. In this case, I also added a couple parameters.
The call is:
if superFunction("reportBehav", long id of this me, pid, "SecondParam", "ChickenParm") then end if

Code: Select all

function superFunction pFunction, pClass, pObjectID
   if the behavior of pClass is not empty then
      // need to yank following put from final code
      put "from Stack superFunction: " & "Class getting super of: " & pClass & cr & "method: " & pFunction & cr & "name ID: " & name of pObjectID & cr after field 1
      
      local tParams
      --THIS SEEMS THE WAY TO DO THIS, IS IT REALLY?
      --params() and param(i) deliver way too much stuff to parse
      put empty into tParams
      if paramCount() > 2 then
         repeat with i = 3 to the paramCount
            put param(i) & comma after tParams
         end repeat
         delete last char of tParams
      end if
      dispatch function pFunction to the behavior of pClass with pObjectID, tParams
      return the result
   end if
   return "unhandled"   --don't know how to really return "unhandled" value
end superFunction
In this case, "behavior of pClass" causes the dispatch to go to behavCL1.
"with pObjectID" guarantees that the behavCL1 method receives the sDoIt object to work with.
Now at the lowest subclass level, you could dispatch to "behavior of pObjectID" and it would be correct and go to the intended superclass method.
BUT...
once you get higher up the chain, behavior of pObjectID is pointing to the wrong class level.
So the correct way to specify the pClass is: long id of this me
That will always go to the next level up the class hierarchy from where the code is.
(we are assuming always calling superFunction from within a Class method (command or function))

In this function, I try a dispatch to "long id of this me" to see where it goes.
behavCL1 does have both a reportBehav command and a reportBehav function.

Code: Select all

dispatch "reportBehav" to the long id of this me
Without an objectID parameter, the target of the reportBehav command changed to the behavCL1 class object, not the sDoIt object.

Next, this function is going to call a command handler in the super class of behavCL1: which is behavCL2, using the stack command: superCommand.

Code: Select all

command superCommand pCommand, pClass, pObjectID
   if the behavior of pClass is not empty then
      // need to yank following put from final code
      put "from Stack superCommand: " & "Class getting super of: " & pClass & cr & "method: " & pCommand & cr & "name ID: " & name of pObjectID & cr after field 1
      dispatch pCommand to the behavior of pClass with pObjectID
      return the result
   end if
   return "unhandled"   --don't know how to really return "unhandled" value
end superCommand
Once again, objectID is needed.
The target received is behavCL2. But the objectID received is that of sDoIt - you find that when printing the "name of pid".
In this command, it prints its script variable (sB2Var) referencing it directly AND through its accessor function dispatched to the pid (sDoIt) object.

Code: Select all

      dispatch function "getSB2Var" to pid
When referencing sB2Var directly, you get the original value initialized by the script.
(you can also call the behavCL2 function getSB2Var() and you get the same value as referencing the script variable directly)

When fetching sB2Var from pid using the above code, you get the value that was set in the reportBehav handler of the behaveSee class - when the target is the sDoIt object.

After returning from the behavCL2 reportBehav, more work is done in the behavCL1 reportBehav and then more is done in behaveSee reportBehav.


So in summary:
You can call superclass methods and have them return to your calling method.
You can have code that gets executed before and after the call to the superclass method.
You use either dispatch or dispatch function and it is critical how you specify the class whose method you want called and that you provide an objectID for the object variables.
You can access superClass fields, there are Class fields and object fields and they are separate.
I suggest putting in getters and setters for all class fields that you wish to access from outside the class.
You call those getters and setters with:
dispatch function "getter" to pid
dispatch "setter" to pid with pValue


In general, if you are using these mechanisms as a C++ equivalent, it is NOT desirable to access the script variables of the "Class objects". That is NOT done in C++ because the Class itself does not exist like a LiveCode object does.
Yes there are static Class fields, available to all their subclasses, but those are for special circumstances since there is only ever one instance of that field.

The superCommand and superFunction handlers are useful because they remind you how to access the superClass and don't eliminate having multiple parameters beyond the objectID.

I hope this was useful detailing some of the differences between LiveCode and C++, and it also shows that there is a very workable, usable means of using C++ concepts in LiveCode (at least for me).

I attached the two LiveCode files that I modified that I referenced at the top of this reply.
Attachments
SuperClassSample.zip
Contains modified addressingBehaviorsChangesTarget.livecode and modified OOPEngine.livecode
(12.52 KiB) Downloaded 36 times

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

Re: Equivalent of classes in LiveCode

Post by mwieder » Fri Aug 28, 2020 2:45 am

Andrew- OK: I've waded through most of that and looked at what you've got in the stacks. You've put a lot of thought into this.
In general, if you are using these mechanisms as a C++ equivalent, it is NOT desirable to access the script variables of the "Class objects". That is NOT done in C++ because the Class itself does not exist like a LiveCode object does.
Yes there are static Class fields, available to all their subclasses, but those are for special circumstances since there is only ever one instance of that field.
Yes. yes.yes.yes.
And there's an easier way to have static class fields.
Here's a modification of your "behaveSee" class that implements sBaseVar as a static field:

Code: Select all

#local sBaseVar = "baseVar"
# sBaseVar is now a static field of this class

command behaveSee
   #put "UI BUTTON class" into sBaseVar
   set the sBaseVar of this me to "UI BUTTON class" 
end behaveSee

command behaveSee.delete
   #put empty into sBaseVar
   set the sBaseVar of this me to empty
end behaveSee.delete

function getSBaseVar
   #return sBaseVar
   return the sBaseVar of this me
end getSBaseVar

command setSBaseVar pNewValue
   set the sBaseVar of this me to pNewValue
end setSBaseVar

By setting/getting a property of the sBaseVar behavior button (that's the "this me" referenced there) you're using what is essentially a static field of the class rather than using a local variable in any of the class instances. Then you can access the getters and setters from the object without having to talk directly to the class/behaviors, or set them directly from outside the class path as necessary.

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

Re: Equivalent of classes in LiveCode

Post by mwieder » Fri Aug 28, 2020 4:13 am

...and with regard to your superCommand and superFunction handlers, note that they're already taken care of by the messageObject handler in the OOPEngine stack script.
If this were C++, then I would have:

behaveSee *sDoIt; //sDoIt is a pointer to an object whose Class is behaveSee.
and here with the OOPEngine you would have

[updated: got the parameters in the wrong order before]

Code: Select all

put newObject("behaveSee", "sDoIt", "button") into sDoIt
where "button" could be any of the possible primitive object types
and the first argument to the newObject function is whatever name you want to give the newly created object.

Doh! I keep reading "sDoIt" as "sDOLT"
Last edited by mwieder on Sat Aug 29, 2020 1:59 am, edited 1 time in total.

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

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Fri Aug 28, 2020 7:11 pm

...and with regard to your superCommand and superFunction handlers, note that they're already taken care of by the messageObject handler in the OOPEngine stack script.
I really wish it was. I started with using that function and was really confused when what I expected did not happen.
Either that, or what I am supposed to use as a parameter for pObject is quite different than I expected.

Initially I was expecting that this was a means to call the superClass method of ther object. What it does is very dependent upon what has been implemented. Also, the Particles program is insufficient to properly test a C++ Classes type program. You REQUIRE at least 3 classes in the hierarchy to see what happens as you attempt to go up the hierarchy.

So if you have sDoIt, whose Class is BehaveSee and you call:

messageObject sDoIt, "reportBehav"

That ends up with a call to the BehaveSee reportBehav function (because it exists) and you do have sDoIt as the Target of the function.
OK, that is fine, it worked.
IF BehaveSee did not have a reportBehav function, you would end up in the next level up the class hiearchy that did have a reportBehav function and have the proper data set. Once again, that works.

If you want to access a superClass's script variable, you can use messageObject sDoIt, "getSomeSuperClassField"
and assuming that function exists in the class hierarchy, it will be called with sDoIt as the Target and return that superClass's field value of the data set sDoIt. The key thing here is that because you are attempting to access a script variable of a superClass, that "getSomeSuperClassField" function is only going to exist in that superClass because the script variable is private to that superClass.

I think the big thing to realize here is that dispatch always starts at the lowest level of the "to object" and works its way up until it finds a handler with the desired name.

Now if from within the BehaveSee reportBehav function, if you want to call the reportBehav function of the SUPER CLASS of BehaveSee and you want to keep the sDoIt object as what you are operating on, the following MUST occur.

dispatch function "reportBehav" to the behavior of long id of this me with sDoIt

If you call: messageObject sDoIt, "reportBehav" from within the BehaveSee reportBehav function - you are calling the same function and end up in an infinite loop (I tried it). There are various types of syntax you can employ at this low level that will get you to the super class (BehavCL1) that will NOT work once you get to BehavCL1 and want to call the methods of BehavCL2. I spent a lot of time confused because I tried them thinking I would get there, but then found out I had the wrong object for the data (LiveCode's implied Target mechanism).

Believe me, I tried to get to a general mechanism that would reliably traverse the class hierarchy with the fewest parameters that I could.
If you are not careful and not explicit, then LiveCode's "Target" mechanism takes over and you don't end up where you expect (at least not where a C++ programmer expects).

Finally, the name. I specifically chose the names I did to LOOK different from normal LiveCode and highlight the difference between a command and a function rather than hide them.

The primary use of superFunction and superCommand is to call the NEXT LEVEL UP in the class hierarchy and arrive there with the proper data set to operate on.
When you write the code for any handler called by superFunction / superCommand, you MUST include a pObjectID parameter and use it as the target of your operations. Otherwise the code will operate on the implied Target, which is likely to be the Class.
Example:

function somethingFun pObjectID, parm1,parm2, ...

If all you want to do is make a call at the same level or allow the code to flow up until it finds the handler then:

dispatch function "whateverFunction" to pObjectID with whatever parameters
and
dispatch "whateverCommand" to pObjectID with whatever parameters

work just fine. That is when your messageObject function works.

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

Re: Equivalent of classes in LiveCode

Post by mwieder » Fri Aug 28, 2020 8:20 pm

Andrew-

Are you trying to simulate function overloading by giving the same name to functions in different scripts?
I don't see the advantage of that here, and you'll end up with weird roundabouts in trying to deal with it, as you're discovering.

I've been remiss in posting the latest build of the OOPEngine and Particles demo.
The FuzzyGravity particle does have three levels of subclassing, and it's working with no problems.
Attachments
OOPMWLivecode.zip
(94.98 KiB) Downloaded 32 times

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

Re: Equivalent of classes in LiveCode

Post by bwmilby » Fri Aug 28, 2020 9:12 pm

aetaylorBUSBnWt wrote:
Fri Aug 28, 2020 7:11 pm
Believe me, I tried to get to a general mechanism that would reliably traverse the class hierarchy with the fewest parameters that I could.
If you are not careful and not explicit, then LiveCode's "Target" mechanism takes over and you don't end up where you expect (at least not where a C++ programmer expects).
One large disadvantage to your approach is that you lose access to the script locals at every level of the class hierarchy since you can never be sure that the code is executing in the proper context. That means you have the define a getter/setter for any variable that is object specific and always dispatch that to the object as you have done.

I started thinking of a way to do it. This is precisely where I was going with defining multiple handlers in every class for the same call to allow the direct passing up the hierarchy if needed. Here's some pseudocode (I'll need to work on an actual example):

Code: Select all

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

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

private command _className.reportBehav pParam1, pParam2
  # do stuff
  dispatch "superClassName.reportBehav" to me with pParam1, pParam2
  # do more stuff
end _className.reportBehav
Using this approach, me is always the actual object. It will be safe to use any of the script local variables since you will always be in the correct context of the object. (In LiveCode, a private command/function is essentially "free" from a performance perspective which is why I structured it the way I did.) I think this is probably the most "with the grain" approach to going up and back down the hierarchy. If you just need to go up, then pass would be a better option since it shortens the message path.

EDIT: Here's a demo stack that demonstrates the concept. I took the SuperClassSample and removed just about everything targeting the abstract class objects directly (and their variables). I also updated the logging to indent and show the flow of the code by handler. The OOPEngine is included as the stack script so it doesn't depend on anything else to work (slightly different than Mark's version).
Attachments
SuperClassCalls.livecode.zip
SuperClassCalls
(4.96 KiB) Downloaded 38 times
Brian Milby

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

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

Re: Equivalent of classes in LiveCode

Post by mwieder » Sun Aug 30, 2020 7:03 pm

As promised, here's my linked-list class and a demo stack.
The class constructor takes an optional argument of "single" if you need a singly-linked list, otherwise defaults to doubly-linked.
Attachments
LinkedListClass.zip
(2.34 KiB) Downloaded 36 times

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

Re: Equivalent of classes in LiveCode

Post by aetaylorBUSBnWt » Mon Aug 31, 2020 2:13 pm

Hi,
Are you trying to simulate function overloading by giving the same name to functions in different scripts?
I don't see the advantage of that here, and you'll end up with weird roundabouts in trying to deal with it, as you're discovering.
Function overloading is exactly what superFunction/superCommand do achieve. It just took me a little time to understand LiveCode's implicit Target mechanism. Once I understood that, I knew to include the pObjectID parameter. The fact that LiveCode includes the separate set of variables when an object inherits a behavior means that they intended this kind of use. It is a powerful addition to the language, one to be taken advantage of. It is too bad that it is not really well documented, understood and accepted.
The pObjectID parameter is no different than how LiveCode achieves its Target parameter or any other object oriented language achieves its "this" or "self" parameter. Since I am not in control of the language, I can't add a hidden parameter to every function/command call, but that is what they are doing, adding a hidden parameter. After that, it is simply a matter of realizing what you want to be the subject of the function and using it. No big deal to me.
I've been remiss in posting the latest build of the OOPEngine and Particles demo.
The FuzzyGravity particle does have three levels of subclassing, and it's working with no problems.
Excellent. It took some digging at the buttons showing on card 2, but eventually I discovered that there is a fuzzyGravity button and a gravity button - placed on top of each other. So I did not think to look for the hidden button.
Yes, this does provide the means to be able to discover function overriding, but it is never done. If I had realized that there were 3 levels of classes there, I might have tried to do more with this code. I believe that these classes only used the "pass" mechanism to go up the hierarchy, so they never showed the opportunities that the "dispatch" mechanism provides that Bernd showed us.
One large disadvantage to your approach is that you lose access to the script locals at every level of the class hierarchy since you can never be sure that the code is executing in the proper context. That means you have the define a getter/setter for any variable that is object specific and always dispatch that to the object as you have done.
LiveCode makes script locals private. OK, I accept that, not a problem once it is known. Private class variables are part of life in C++ and Objective C. I can't access the class variables directly in the entire Cocoa class hierarchy and it is not a big deal, it just needs to be known. Apple's Objective C automatically creates getters/setters for all public class variables that you are expected to use rather than directly accessing them. It becomes a habit to write "getVariableName" and "setVariableName" and expect it to work.
Now that I know about the hidden context concept, it is much easier to code to access the desired context.

I haven't had a chance to to look through Brian's rewrite of SuperClassSample, but I certainly will.

Thanks,
Andrew

Post Reply

Return to “Getting Started with LiveCode - Experienced Developers”