Shop OBEX P1 Docs P2 Docs Learn Events
Function Pointers in Spin2 - Page 2 — Parallax Forums

Function Pointers in Spin2

24

Comments

  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 22:37
    Rather than ask questions like "how can we have function pointers in Spin?" or "how can we do inheritance?" or "how can we do language feature X,YZ?" perhaps we should ask "What does Spin need that will make it even easier for it's intended audience of beginner programmers?"

    Over the years I have noticed that a recurring question from newbies to Spin (and programming at all) goes something like this:

    "How do I send messages to FullDuplexSerial from a piece of code in an object that is not the one that included FDS?"

    Think about it from a newbie perspective. He can write seral.tx("Hello world") in one place and that's OK but he cannot write the same thing somewhere else. Makes no sense whatsoever does it.

    Said newbie does not want to know about function pointers, or passing objects or inhertance or any other such complication. He just wants to write what he knows works in a place where it does not.

    I hate to say this but it seems to me the easist way to fix our newbies problem is to have the possibility of globaly scoped objects!

    Job done. His serial.tx("hello world") now works everywhere as he rightly expects it to.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 22:38
    David Betz wrote: »
    I guess what I'd really like to see is support for inheritance and passing object references as parameters to functions. This would make objects first-class data types. I guess that's not likely to happen though because it would pretty much require a type system.

    The compiler can keep track of objects and their references, I'm sure the compiler has a symbol table to keep track of variable reference already, this would be an extension.

    It's not far fetched to implement inheritance (single or multiple? definitely only one level of derived class though) and object passing.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 22:56
    David Betz wrote:
    I think Chip was trying to avoid 64 bit pointers.

    'Seems like you ought to be able to pack "object/instance #17:pub #2" in 32 bits or less. It's just a table lookup from there.

    I think function references would be easier to implement than object references. The reason is that, in the latter, the object code would have to include a dictionary (i.e. public method symbol table) for each object, unless you also enforced a rule that object methods (e.g. tx and rx would not only have to be named the same but occur in the same order across all such objects.

    -Phil
  • msrobotsmsrobots Posts: 3,709
    edited 2013-05-10 23:09
    ok,

    lets just summarize what SPIN currently does and where the problems are why we need something like function or object pointer.

    As long as your object does not have VAR sections you can include it into all objects needed and it will be pruned down by the compiler to just one instance.

    That can take care of some formatter-object like Phil is talking about, but you have to include it into every object. (and adapt for things like out vs tx etc). uncool.

    You CAN call a spin-function via cognew - alas it is a own instance with a own stack running on another cog/spin-interpreter. also uncool.

    Throwing the whole OO Paradigma with inheritance, overloading, casing and a type-system onto SPIN would be nice, but IMHO not feasible. Keep it simple.

    Since I am german I might be a little blue-eyed here but is it not possible to get a pointer to an existing instance of a object and pass that thru to another sub-object?

    As far as I understand this, the dynamic object loader DOL is doing this the hard way, iterating thru SPINs HUB memory to pick those values out of it and simulating a call?

    So can not the compiler do the same?

    As Phil said, sio.tx refers to a instance and not a class. Even if we would need a 64bit pointer couldn't take the compiler take care of this?

    we do not need a fullblown type system for this, just one more type: 64bit function/object-pointer.

    I would be fine with just being able to use a function-pointer like sio.rx inside a call to another sub-object, but having a reference to the whole object sio would be better.

    Enjoy

    Mike
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 23:09
    Could someone explain to me in what possible way function references would be useful?

    This would make Spin a bit of an odd ball language. Spin only has objects and methods, there are no free standing functions. So a pointer to a method without any knowledge of it's instance is, well, odd ball. You cannot do that in C++ or Java for example.

    That method that you are about to call through a pointer had better not be using any non-local variables or something is going to explode.
  • cgraceycgracey Posts: 14,206
    edited 2013-05-10 23:09
    I think function references would be easier to implement than object references. The reason is that, in the latter, the object code would have to include a dictionary (i.e. public method symbol table) for each object, unless you also enforced a rule that object methods (e.g. tx and rx would not only have to be named the same but occur in the same order across all such objects.

    -Phil

    Exactly. That's why object pointers would be difficult. Method pointers, though, which support proper state context would be doable.

    Here's how it could work:
    ***** top-level object
    
    VAR
    
      long mptr[3]     'method pointer, holds VBASE, PBASE, and PUB/PRI index number
    
    OBJ
    
      serial : "serialport"
      print  : "printvalue"
    
    PUB Start
    
      serial.start(31, 30, 19200)
    
      mptr := @out             'set mptr to current VBASE/PBASE and index of 'out' method (3 longs)
    
      print.setptr(@mptr)      'pass mptr address to 'print' object's 'setptr' method
    
                               'at this point, 'print' object has full pointer to 'out' method, so
                               'it can pass data back to this object
    
      print.binary($FFFF)      'print binary value to 'out' method
    
    
    
    PRI out(chr)
    
      serial.tx(chr)
    
    
    
    
    ***** "printvalue" object
    
    VAR
    
      long outptr
    
    PUB setptr(p)
    
      outptr := p
    
    PUB binary(value)
    
      repeat 32
        value <-= 1
        @outptr("0" + (value & 1))
    

    Will this not deliver the desired goods?

    When the compiler sees @variable at the start of a line, it knows this is an indirect 'object.method' call.
    When the compiler sees @method on the right hand of an assignment, it knows this must be a 3-long write into a long array to establish an 'object.method' pointer.
    There are no caveats with VAR/DAT sections on either side of the usage.
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 23:21
    If the desired goods are complexity and obfuscation and making things impossible to debug, yes:)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 23:22
    cgracey wrote:
    Will this not deliver the desired goods?
    Yes, that would work. But would there be any way for behind-the-scenes magic to make print.binary(@out, $ffff) work? ("Behind-the-scenes magic" is what I meant by complicating the backend to make the frontend simple.)
    heater wrote:
    Spin only has objects and methods, there are no free standing functions.
    I think "function" and "method" are being used interchangeably here. At least that's been the case in my posts.

    -Phil
  • cgraceycgracey Posts: 14,206
    edited 2013-05-10 23:28
    Yes, that would work. But would there be any way for behind-the-scenes magic to make print.binary(@out, $ffff) work?

    Would this suffice?
    ***** top-level object
    
    VAR
    
      long mptr[3]     'method pointer, holds VBASE, PBASE, and PUB/PRI index number
    
    OBJ
    
      serial : "serialport"
      print  : "printvalue"
    
    PUB Start
    
      serial.start(31, 30, 19200)
    
      outptr := @out             'set outptr to current VBASE/PBASE and index of 'out' method (3 longs)
    
      print.binary(@outptr, $FFFF)      'print binary value to 'out' method
    
    
    
    PRI out(chr)
    
      serial.tx(chr)
    
    
    
    
    ***** "printvalue" object
    
    PUB binary(outptr, value)
    
      repeat 32
        value <-= 1
        @outptr("0" + (value & 1))
    

    The thing is, there are three longs that must be conveyed to form a complete 'object.method' pointer. To fake it in a parameter list would be a little misleading, not to mention much less efficient than just passing the address of the method pointer, itself.
  • msrobotsmsrobots Posts: 3,709
    edited 2013-05-10 23:29
    Yes.

    That would do very nicely for most of my needs...

    another thing I am missing is
    adr := sio@buffer
    like
    costant := sio#CR
    

    Enjoy!

    Mike
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 23:30
    Yes, "function" and "method" get used interchangeably a lot. Makes no difference to my arguments.

    By taking a pointer to a method (only) one has effectively made it a function as it has no object context anymore.

    However I see from Chip's example he has in mind a "mega-pointer" that gets you to the object context as well. So it is more than a function pointer.

    What I don't like about Chip's example is the extra layer of indirection with mptr and having to bounce through the current objects out method when making the call from inside print to serial.out. All seems rather messy.
  • Cluso99Cluso99 Posts: 18,069
    edited 2013-05-10 23:30
    cgracey wrote: »
    Exactly. That's why object pointers would be difficult. Method pointers, though, which support proper state context would be doable.

    Here's how it could work:
    ***** top-level object
    
    VAR
    
      long mptr[3]     'method pointer, holds VBASE, PBASE, and PUB/PRI index number
    
    OBJ
    
      serial : "serialport"
      print  : "printvalue"
    
    PUB Start
    
      serial.start(31, 30, 19200)
    
      mptr := @out             'set mptr to current VBASE/PBASE and index of 'out' method (3 longs)
    
      print.setptr(@mptr)      'pass mptr address to 'print' object's 'setptr' method
    
                               'at this point, 'print' object has full pointer to 'out' method, so
                               'it can pass data back to this object
    
      print.binary($FFFF)      'print binary value to 'out' method
    
    
    
    PRI out(chr)
    
      serial.tx(chr)
    
    
    
    
    ***** "printvalue" object
    
    VAR
    
      long outptr
    
    PUB setptr(p)
    
      outptr := p
    
    PUB binary(value)
    
      repeat 32
        value <-= 1
        @outptr("0" + (value & 1))
    

    Will this not deliver the desired goods?

    When the compiler sees @variable at the start of a line, it knows this is an indirect 'object.method' call.
    When the compiler sees @method on the right hand of an assignment, it knows this must be a 3-long write into a long array to establish an 'object.method' pointer.
    There are no caveats with VAR/DAT sections on either side of the usage.
    Yes. This is precisely what we need. And this is quite simple and easy to understand.

    If spin gets too complex, then we may as well abandon it, and push into C.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 23:32
    Chip, I guess I'm confused as to why the double indirection is necessary. Couldn't @out simply be made to point to the three-long table?

    -Phil
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 23:33
    I don't see function references being anything more than a hack, object references, while more difficult, are significantly more useful.

    The syntax Chip posted was not simple and straightforward, it relied on ~magic~, which is the worst possible thing you can do.

    The only reason to continue down the function pointer road is if SPIN isn't getting inheritance and object passing, but then I'd rather just hack something more intuitive in PASM or write good looking code.
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 23:34
    Chip,

    Your first example was better, in the last I have to include the @mptr parameter everytime I print something which might be often.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 23:34
    Chip, I guess I'm confused as to why the double indirection is necessary. Couldn't @out simply be made to point to the three-long table?

    -Phil

    Why do we have to point to anything? Make the language abstract this completely from the programmer, then the backend can do 3 longs if it needs to. By that time you are better off just pointing to an object.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 23:36
    pedward wrote:
    I don't see function references being anything more than a hack, object references, while more difficult, are significantly more useful.
    I guess we can disagree on that. :) Method references are easy to grasp without invoking a bunch of OOP stuff that Spin has (mercifully) left out. At least that's my take.

    -Phil
  • cgraceycgracey Posts: 14,206
    edited 2013-05-10 23:38
    Chip, I guess I'm confused as to why the double indirection is necessary. Couldn't @out simply be made to point to the three-long table?

    -Phil

    The 3-long pointer is unique to the instance of the object establishing it, so it must live in VAR space and be resolved at run-time.
  • cgraceycgracey Posts: 14,206
    edited 2013-05-10 23:39
    Heater. wrote: »
    Chip,

    Your first example was better, in the last I have to include the @mptr parameter everytime I print something which might be often.

    I know. The second example was superfluous and meant to show more compact (but less efficient) code to somewhat satisfy Phil's desire.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 23:40
    heater wrote:
    Your first example was better, in the last I have to include the @mptr parameter everytime I print something which might be often.
    I didn't like the first example at all, because the call to setptr hides the referrent from subsequent method calls. Hidden variables can cause debugging headaches. Moreover, with multiple output streams, you'd have to change gears every time you print to a different output. It's a difference between "aim at A; shoot; aim at B; shoot" and "shoot at A; shoot at B." The latter seems simpler to me.

    -Phil
  • cgraceycgracey Posts: 14,206
    edited 2013-05-10 23:44
    Heater. wrote: »
    What I don't like about Chip's example is the extra layer of indirection with mptr and having to bounce through the current objects out method when making the call from inside print to serial.out. All seems rather messy.

    You could have 'serial' make the "mega-pointer" and pass you its address, which you hand off to 'print', instead of the pointer to your 'out'.
  • cgraceycgracey Posts: 14,206
    edited 2013-05-10 23:57
    If 'serial' had a method to make and give you the address of the "mega-pointer", then it could look like this:
    ***** top-level object
    
    OBJ
    
      serial : "serialport"
      print  : "printvalue"
    
    PUB Start
    
      serial.start(31, 30, 19200)
    
      print.binary(serial.outptr, $FFFF)      'print binary value to 'out' method
    
    
    ***** "printvalue" object
    
    PUB binary(outptr, value)
    
      repeat 32
        value <-= 1
        @outptr("0" + (value & 1))
    

    Of course, it would be faster to not do 'serial.outptr' every time you printed something, but rather do that once and save that pointer address in a VAR long.
  • cgraceycgracey Posts: 14,206
    edited 2013-05-11 00:03
    The '@methodptr' on the start of a line could be overloaded to accept '@methodptr[index]' which would add to the PUB/PRI index, so that you could call a range of methods, starting at the original pointer's index.
  • cgraceycgracey Posts: 14,206
    edited 2013-05-11 00:04
    What are you guys thinking now? You all went quiet.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-11 00:05
    That's getting better, IMO. Is the "@" in @outptr("0" + (value & 1)) really necessary? It kind of carries the reverse semantics of other @'s. The parens should be enough context to denote a method call, with method_name() being okay for parameterless calls.

    -Phil
  • cgraceycgracey Posts: 14,206
    edited 2013-05-11 00:07
    msrobots wrote: »
    Yes.

    That would do very nicely for most of my needs...

    another thing I am missing is
    adr := sio@buffer
    like
    costant := sio#CR
    

    Enjoy!

    Mike

    Mike,

    That could be used to make a pointer to a child object's method! Or, used to get the address of some VAR or DAT resource. Wait... it could do ALL the above, as the compiler would know the context.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-11 00:07
    cgracey wrote:
    What are you guys thinking now? You all went quiet.
    I know you want to stay up all night to discusss this :), but I'm afraid I'm starting to fade. More in the morning?

    -Phil
  • SapiehaSapieha Posts: 2,964
    edited 2013-05-11 00:08
    Hi Chip.

    I'm still thinking.
    As I never analysed SPIN interpreter so much --- Need much thinking before I can say anything.

    My concerns now are Good Assembler -- Spin are second part for me.
    And as P2 give me possibility to program in RAW assembly -- That is my first choice


    cgracey wrote: »
    What are you guys thinking now? You all went quiet.
  • cgraceycgracey Posts: 14,206
    edited 2013-05-11 00:08
    That's getting better, IMO. Is the "@" in @outptr("0" + (value & 1)) really necessary? It kind of carries the reverse semantics of other @'s. The parens should be enough context to denote a method call, with method_name() being okay for parameterless calls.

    -Phil

    You're right, Phil! That's a lot better.
  • Heater.Heater. Posts: 21,230
    edited 2013-05-11 00:09
    Starts to look more like it, except contrary to Phil I would rather see:
    print.setOutStream(serial.outptr)
    print.binary($FFFF)
    

    Now what happens when I want to use serial from something a bit more challenging that print?

    I might have some complex protocol driver that can be used with any serial device. Now I need a mega_pointer for "in" as well and perhaps other methods on the serial device. This could get out of hand.
Sign In or Register to comment.