SPIN Pub&Var scope limitations
Rayman
Posts: 14,791
It seems that public methods and variables of a parent object are not·accessible by child objects.· I see ways of making variables of the parent accessible to the child.· But, I don't see any (simple) way for a child object to access a public method of the parent.· Am I missing something?
If this is true, this is fundamental limitation of the SPIN language that should be mentioned in the manual!· The manual just says that "Public methods can be accessed outside of the object itself"...· Doesn't mention that it's a one-way street...
(In C++, I often pass a pointer to the parent object in calls to the child so that the child has access to the parents methods and variables.)
If this is true, this is fundamental limitation of the SPIN language that should be mentioned in the manual!· The manual just says that "Public methods can be accessed outside of the object itself"...· Doesn't mention that it's a one-way street...
(In C++, I often pass a pointer to the parent object in calls to the child so that the child has access to the parents methods and variables.)
Comments
There a resons for this situation, founded in simplified compiler build techniques and more easy run-time environment. Note that EVERYTHING has to fit within a single COG!!
It's very clear that the "object" structure was intended as a way to support packaged I/O drivers, not the general notion of objects as seen in many modern "advanced" programming languages. Unfortunately, it's almost powerful enough (but not quite) to support abstract structures and the costs for making the compiler changes are well beyond the resources available. Fortunately, a lot of the changes that would be most useful would not require any changes in the interpreter or interpretive code, things like a preprocessor for source inclusions and conditional compilation, like a change to the OBJ section to support shared objects, abstract objects, and multilevel object references.
I think, we need a generell procedure to send data elements to operators. Input A, processed by B is outputted at C. Normaly: C := B(A). That is, what is now. Fetch Input A, determine appropriate operater, send the data to the operator, do the manipulation, determine output, send data to the output. This scheme is more complicated, but only for simple applications. Whenever the application becomes more complex, divide and conquer is the solution of choice. ?
I have a very basic GUI interface with several forms and controls that I'm trying to split from one huge Spin file into several smaller ones. But, this is proving to be very challenging, given these scoping rules...
It would also allow many cross checks, e.g. unused variables, easy introduction of quasi-structs... With a little bit more effort it mght even generate SPIN bytecode directly...
Mike is correct that many "wishes" can be fulfilled by the existing byte code, when generating wrappers under the hood. Even routine-variables are possible!!! Maybe even true arrays (multiple dimensions, bounds checking....)
e.g., In the child I have
PUB GetForm:Form
· return @form1
....
DAT
form1 word
....
Where, I'm attempting to pass the address of the data at "form1" back to the parent using the "GetForm" method...
Post Edited (Rayman) : 10/24/2007 5:28:17 PM GMT
I want to get the address of a string in the child's DAT section using:
fStr:= @@(word[noparse][[/noparse]pF+4])······························ '· where [url=mailto:pF=@form1]pF=@form1[/url]
And, the child's DAT section has this:
sTitle· byte "Dual Delay Generator",0
form1·· word
······· byte· 5· 'top
······· byte· 8 'left
······· byte 36· 'height
······· byte 46· 'width
······· word @sTitle
I think it's this @@ thing that's not working...
It worked when this data was in the parent's DAT section, but doesn't work now that I moved it to the child...
Obviously, this is a one-time-only initialization process
You could also use the notation:[noparse][[/noparse]code]
form1[noparse][[/noparse] 2 ] := @@sTitle[noparse][[/noparse]/code]
You've declared form1 as a word, so you can use array notation and word indexing.
Post Edited (Mike Green) : 10/24/2007 6:44:04 PM GMT
I did find a work-around for the above problem by calling an "init" method in the child to do this:
word[noparse][[/noparse]pF+4]:=@@(word[noparse][[/noparse]pF+4]) ' where pF=@form1
And then changing the parent's code to :
fStr:= word[noparse][[/noparse]pF+4] ' where pF=@form1
form1 [noparse][[/noparse] 2 ]:=@@form1 [noparse][[/noparse] 2 ]
It is a lot cleaner...
I see what deSilva means with the [noparse][[/noparse] ] problem now!
Post Edited (Rayman) : 10/24/2007 7:02:56 PM GMT
Simplest: pick a spot in the hub and drop the address there. Then any other cog can go get the address. But really you want to know what the string (data) is.
The usual way is to have the parent set up the hub data array. Then it passes a pointer to the array that the child uses to create its own view. Child then drops data into array. Parent reads data (usually when a flag shows data is ready).
I got to several lines like this:
bytemove(@ buttonPinA+8,num.ToStr(CheckVal(@ chkEnableA,long [noparse][[/noparse] i+0 ] ),num#DDEC),3)
Where buttonPinA and chkEnableA would be in the grandchild, num is an object in the child, CheckVal is a method in the child, and i refers to data in the parent.
It's just really unworkable to split this up...
- To split your things into small "objects" to allow the easy access to methods and constants
- to use GETTERS and SETTERS as generally recommended in pure OOP.
As getters and setters are immensely uneffective in SPIN, it might suffice to use them to deliver vector addresses.
The main issue I still see is that you try to use "structs" (or "records" as they are called in Pascal) where SPIN is most obviously not made for. But this can also be overcome by a suit of getters and setters....
I know how you feel. In my case I accepted it, put what I had to one side, re-designed with objects how they should be, re-wrote from the top down and put the code in where it should be with the appropriate changes.
Starting from a clean sheet was far easier than trying to alter things all over the place. I wasted three days trying to move things from one object to another, getting confused and had to start again. Did the whole thing from scratch in half a day.
There are a lot of those Getter and Setter routines passing things down the object chain and pulling things back up but it does work.
My goal with this exercise was to make the code more readable and easier to follow. While I believe that it could be split in the way you say, it would make the code virtually impossible to follow and make debugging an absolute nightmare...
I really could use this "#Include" functionality though. Hopefully, they'll add it one day...
Unfortunately modularisation by simply dropping things into objects doesn't work because of not being able to call parent methods and no easy way to call a method in another object.
I find that code therefore tends to percolate up the objects into the top-level with the result of one huge Spin file. Without #include there's no way to split code into logical or easily manageable files, it's all or nothing with objects having only limited use in code partitioning.
Regarding debugging, that's a particular nuisance, because I have all the routines which could do debugging via TV, VGA, LCD, serial or USB, but they aren't accessible from anywhere where I need them :-(
·
I am not quite sure what you are talking about...
It is true, that no circular references are allowd (due to SPIN's simplistic approach)
However it is well known, that circular references are rarely needed and in most cases are an artefact from sloppy design.
You can resolve all circular references by putting the concerned methods into one object... But granted, the worst case scenario of course will be the "all-in-one object"
If I have a PUB TxNumber(n) in SerialPortRoutines I cannot call it from within the VideoDriver. If we had object/method pointers I could pass that down, but we don't :-(
No circular references, I'm just trying to cross object domains.
@ mirror : I'll check out SerialMirror as it may be what I need, or can be adapted to what I need. Thanks.
I did a search for SerialMirror in the object exchange, and didn't find it, so here's a link:
http://obex.parallax.com/objects/189/
It was originally described in this post:
http://forums.parallax.com/showthread.php?p=649541
It is a misdesign in some library objects that Mirror addressed after one of those discussions in its SerialMirror.
In fact you can't have both at the ame time: true instantiation and general usage.
THIS is the shortcoming of SPIN. But there is still Mirror's solution: Offer TWO variants.
Having looked at serialMirror ( found on obex, I think it was on page 3 of interface, I2C, SPI, serial ... ) and having slept on it I realise I knew the solution all along and it is described in other current threads, but either a key part of the basic explanation was missing or I missed it, or didn't relate what was said as the solution -
One cannot call up to a parent, nor can one call across to another object, but the object to be called can be included as a sub-object where ever needed multiple times. The object must be designed to allow that ( uses only DAT not VAR ). The code size increase is negligible because of the way the compiler handles multiple same-object inclusion.
What's been said here and elsewhere amounts to that but it wasn't in the form which triggered that basic realisation.
It is such an important part of the 'design paradigm' of Spin coding that I think it deserves a tutorial explaining it - "How to design objects which can be used anywhere they are needed" - what the consequences and limitations are. If #if conditional compilation is ever included in the Spin language this would have some adverse effects on code size if used inappropriately in multiple use objects.
I knew the solution but couldn't see the solution. Sometimes it just needs a kick in the right direction to get there. SerialMirror was the kick I needed.