Shop OBEX P1 Docs P2 Docs Learn Events
SPIN Pub&Var scope limitations — Parallax Forums

SPIN Pub&Var scope limitations

RaymanRayman Posts: 14,162
edited 2007-10-25 14:48 in Propeller 1
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.)
«1

Comments

  • deSilvadeSilva Posts: 2,967
    edited 2007-10-24 13:40
    Yes this is a limitation. There are many more (I once counted 14) - including what we collected here as "pitfalls" - not very openly spoken about in the manual. Things an "advanced" programmer espects from an "advanced" programming language (e.g. Python)

    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!!
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 14:16
    Also just verified that you can't access grandchildren's variables or methods... (i.e., "child.grandchild.method(i)" doesn't work).
  • Mike GreenMike Green Posts: 23,101
    edited 2007-10-24 15:06
    Yeah. It's very easy to think of Spin as an "advanced" programming language when it's not really. It's very much in the flavor of early C or early Pascal if any of you remember those. It (Spin) has a pretty rich set of operators, an adequate set of control structures (if / repeat / case), and a very C-like value-parameter-only function call. There are scalars and arrays and no explicit pointer type, but you can use integers for pointers.

    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.
  • ErNaErNa Posts: 1,749
    edited 2007-10-24 15:50
    Working with the propeller will lead to a change of programming paradigm. This is especially true, if more chips are connected. Then, common memory no longer exists and can not be used. Wether we bring the code to the data or the data to the code.

    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. ?
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 15:55
    I just wish these limitations were more clearly documented...

    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...
  • deSilvadeSilva Posts: 2,967
    edited 2007-10-24 16:19
    I do not remember who made this proposal: Use an independant preprocessor, pecompile all your code into one huge SPIN file.
    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....)
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 17:19
    It also seems that I can't pass the address of data in the DAT section of a child back to the parent...

    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
  • deSilvadeSilva Posts: 2,967
    edited 2007-10-24 17:23
    What's not working? I seem to have no problem with it...
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 17:26
    Hmm... Let me take that back. My code doesn't work, but that's not why...
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 17:34
    Ok, here's my problem:

    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...
  • Mike GreenMike Green Posts: 23,101
    edited 2007-10-24 18:38
    The problem you're having is that the @@ operator is object specific since the compiler doesn't know the address of the DAT section when the code is compiled. If you execute the @@ in the parent object, the interpreter uses the base address of the parent's DAT section rather than the base address of the child's. Usually this sort of thing is done by making access methods in the child that are called by the parent. The @@ operator then gets executed in the child's context. In your previous attempt to do this (in your last post), try this instead:
    word[noparse][[/noparse]@form1+4] := @@sTitle
    


    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
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 18:40
    Well, it seems the @@ operator only works for data in the object where it is defined (unfortunately).

    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
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 18:41
    Sorry Mike...
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 18:47
    Mike: I am switching over to the array notation, as you suggested, e.g.:

    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

  • Mike GreenMike Green Posts: 23,101
    edited 2007-10-24 18:49
    Another thing that sometimes helps is to just have a method in the child that returns the base address of the DAT section or the base address of the table (in which case, you define all the address constants as relative to the start of the table) and you do all your own accessing. In something like you have, I'd keep separate areas with the text strings and the table proper. The array notation helps here since you can use word indices. In your case, the form table reference looks like word[noparse][[/noparse] pF ][noparse][[/noparse] 2 ] to get the offset of the string in your string table. If you use pS as the pointer to the string table, the address of the "Dual ... Generator" message would be word[noparse][[/noparse] pF ][noparse][[/noparse] 2 ]+pS. In your table, instead of using @sTitle, you'd use sTitle-sBase where sBase is the start of the string table.
  • Fred HawkinsFred Hawkins Posts: 997
    edited 2007-10-24 18:49
    ? It looks like you are seeking to find out the cog address of some data in a 2nd cog that's running an assembly program. I don't think any other cog can find that out unless you set up a way for the second cog to let it be known.

    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).
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 19:38
    Ok, I give up...

    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...
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 19:53
    One thing that would be awfully nice (in case a Parallax people are out there...) is if I could "#include" source from other files into my code. Something like the "File" command, but for code, not just byte data, would be very nice!
  • deSilvadeSilva Posts: 2,967
    edited 2007-10-24 20:47
    @Rayman: I can fully understand your complains. However the elementary workaround - and it is not more than a workaround! - is:

    - 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....
  • hippyhippy Posts: 1,981
    edited 2007-10-24 22:58
    Rayman said...
    It's just really unworkable to split this up...

    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.
  • RaymanRayman Posts: 14,162
    edited 2007-10-24 23:17
    Well, I'm resigned to having almost everything in one big Spin file. Once I pick a really small font size in the editor, it's not so bad...

    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...
  • deSilvadeSilva Posts: 2,967
    edited 2007-10-24 23:23
    Rayman said...
    Once I pick a really small font size in the editor, it's not so bad...
    Just BTW: It is not general knowledge that this is easily done by Control-CursorUp and Control-CursorDoen
  • Graham StablerGraham Stabler Posts: 2,507
    edited 2007-10-24 23:28
    Or ctrl-rollmouse
  • hippyhippy Posts: 1,981
    edited 2007-10-24 23:57
    Rayman said...
    My goal with this exercise was to make the code more readable and easier to follow.

    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 :-(
  • mirrormirror Posts: 322
    edited 2007-10-25 00:29
    hippy said...
    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 :-(
    For serial, have a look at SerialMirror in the object exchange. It's my primary tool for debugging, as I'm able to drop it in anywhere :-).
    ·
  • deSilvadeSilva Posts: 2,967
    edited 2007-10-25 00:48
    hippy said...
    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 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"
  • hippyhippy Posts: 1,981
    edited 2007-10-25 04:46
    @ deSilva : I mean when I have an object which contains my serial handling code and I have another object to debug ...

    
    OBJ
     
      serial : "SerialPortRoutines"
      video : "VideoDriver"
    
    PUB Start
      serial.start(30,31)
      video.start(12)
    
    
    



    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.
  • mirrormirror Posts: 322
    edited 2007-10-25 06:39
    @ 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
  • deSilvadeSilva Posts: 2,967
    edited 2007-10-25 06:48
    hippy said...
    @ deSilva : I mean when I have an object which contains my serial handling code and I have another object to debug ...
    But that the most simple case of all, many times talked about!
    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.
  • hippyhippy Posts: 1,981
    edited 2007-10-25 13:42
    Unfortunately I haven't read everything which has ever been written about the Propeller, have missed things which are in threads I haven't looked at, and when starting and learning not everything sinks in or flags itself as important.

    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.
Sign In or Register to comment.