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

Function Pointers in Spin2

cgraceycgracey Posts: 14,133
edited 2013-05-24 11:30 in Propeller 2
I am thinking about how to implement function pointers in Prop2 Spin.

Is it needful that function pointers can point outside of the object?

I ask because doing function pointing within an object is straightforward, but having function pointers work beyond the scope of the object is difficult - it requires two 32-bit pointers, exceeding a handy long.
«134

Comments

  • jazzedjazzed Posts: 11,803
    edited 2013-05-10 18:04
    cgracey wrote: »
    Is it needful that function pointers can point outside of the object?

    Wouldn't that break Spin's scope rules?

    An object "pointer" or reference would allow calling a public function in one object from another object. That would be kind of like a function pointer.

    I would be very interested in inheritance if you can do it. Inheritance would allow one object to extend another object's capability instead of having to create another set of functions to do the same things. Today, one has to completely replicate an object to extend it, and that makes for some wasteful code. Inheritance would allow making objects that have small variations in functionality. If one could over-load a given function in an inherited object that would be even better. Constructors, etc... not necessary.
  • cgraceycgracey Posts: 14,133
    edited 2013-05-10 18:13
    jazzed wrote: »
    Wouldn't that break Spin's scope rules?

    An object "pointer" or reference would allow calling a public function in one object from another object. That would be kind of like a function pointer.

    I would be very interested in inheritance if you can do it. Inheritance would allow one object to extend another object's capability instead of having to create another set of functions to do the same things. Today, one has to completely replicate an object to extend it, and that makes for some wasteful code. Inheritance would allow making objects that have small variations in functionality. If one could over-load a given function in an inherited object that would be even better. Constructors, etc... not necessary.

    It would break the scope rules.

    The thing is, when pointing to another object's PUB/PRI, you need to point to THREE things: that object's code base, that object's variable base, and that object's PUB/PRI. This would take two longs and a byte. It's not very convenient.
  • Dave HeinDave Hein Posts: 6,347
    edited 2013-05-10 18:39
    There's an object in the obex that implements method pointers for P1. It's located at http://obex.parallax.com/object/384 . The method pointer struct uses 4 words (2 longs) to contain the PBASE, VBASE, method starting address and the number of bytes used for the local variables. P2 would require more bits to handle the larger address space. The compiler could hide the struct space from the programmer, and just provide a pointer to the struct. This would be similar to how the STRING command provides a pointer to the location where the string is stored.
  • jazzedjazzed Posts: 11,803
    edited 2013-05-10 18:52
    By the way Chip,

    We can overload an object today in Spin because there is no object array bounds checking. This effectively gives us a function pointer. Basically we make an object array of one and add other objects to the list, then choose the object to deliver the function by using an out of bounds object array index.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 19:16
    I couldn't come up with any intelligent things to add, mostly because I can't see the value of function pointers in SPIN.

    Steve is right, IMHO there isn't any value in pointing to a function, I abhor function callbacks because they are ugly and error prone. I've seen some bad interfaces that used the callback model to handle async I/O.

    I would think that having the ability to pass object references would have more value for any callback mechanism. I'm not certain this isn't permitted in SPIN now.

    What would make this more useful is the ability to declare objects as local variables instead of requiring them to always be global level entities. I would refer to these objects as anonymous objects, since they only live in the scope of the stack frame.

    I am willing to be convinced that function pointers are of use, but I think this could make SPIN messy and only be applicable to 1:100 programmers, perhaps even worse odds than that. I've learned that sometimes it's best to avoid adding complex features, only a very small percentage of people can understand and exploit those complex features.

    I'm also interested in STRUCTs and UNIONs. A union allows you to alias a variable space, just like you can with labels in PASM, but often times you will do things like byte address a portion of a long, or bit address a portion of a byte, it makes high level language operations more readable and you focus less on manipulating bits and bytes in the code you write.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 19:30
    cgracey wrote:
    Is it needful that function pointers can point outside of the object?

    Absolutely yes! One common usage would be an output formatter object, to which you pass a pointer to a character output routine that exists yet in another object.

    -Phil
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 19:57
    Absolutely yes! One common usage would be an output formatter object, to which you pass a pointer to a character output routine that exists yet in another object.

    -Phil

    The proper context for such usage would be to have an object (class) that implements a standard interface, then you pass a pointer to the object. This would go nicely with Steve's request for inheritance so you can subclass a standard formatter object. In practice you may end up only writing code for that one function, but you pass it as an object instance.

    Function pointer callbacks appear more often in non-OOP languages because OOP brought order and abstracts the callback mechanism away from the programmer. It may all work very similar at the bytecode level, but you aren't forcing the programmer to use archaic mechanisms in a modern language.
  • cgraceycgracey Posts: 14,133
    edited 2013-05-10 20:17
    Thanks for thinking about this, Guys.

    Here's what can be done very simply and elegantly with one long: A simple pointer to any PUB/PRI without any variable base or code base, which would mean that the PUB/PRI can only use local variables and make no references to its own object's DAT or VAR sections, or make any PUB/PRI calls, unless they are by pointer, as well. Is that even useful, though?

    Maybe we just need a special PUB/PRI pointer variable type that can be assigned and utilized, only (for a full two-long-plus-one-byte structure for tracking the whole context).
  • SRLMSRLM Posts: 5,045
    edited 2013-05-10 20:43
    One use case where function pointers might be handy is state machines: http://stackoverflow.com/questions/1647631/c-state-machine-design
  • David BetzDavid Betz Posts: 14,511
    edited 2013-05-10 20:46
    I think object pointers would be more useful than function pointers. One thing that frustrated me about Spin when I first started using it was all of the terminal-like objects that each contained code functions like out, dec, hex, etc. It might be nice to be able to pass an object reference into generic implementations of those functions to do the low-level I/O. That way the same formatted output object could be used for all terminal-like output objects. I guess the same could be done by using inheritance if that were available.
  • cgraceycgracey Posts: 14,133
    edited 2013-05-10 21:09
    David Betz wrote: »
    I think object pointers would be more useful than function pointers. One thing that frustrated me about Spin when I first started using it was all of the terminal-like objects that each contained code functions like out, dec, hex, etc. It might be nice to be able to pass an object reference into generic implementations of those functions to do the low-level I/O. That way the same formatted output object could be used for all terminal-like output objects. I guess the same could be done by using inheritance if that were available.

    So, it would be good if an object's functions could be called, but have a mechanism for those functions to call back to the calling object for I/O operations, for example?

    I think that would mean instantiating an object from within your object, then handing it a pointer, so that it could call back to your object for some required purpose. That would come down to a function pointer, wouldn't it? I mean a function pointer that sets up the context (PBASE/VBASE) as well as the PUB/PRI number for that object. In contrast, an object pointer would contain just the PBASE/VBASE, but not a specific PUB/PRI, as that would be selectable. But then how would the selection be accounted for, being just an index. I think the index built in would be more useful, don't you?
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 21:28
    Jazzed,
    I would be very interested in inheritance if you can do it. Inheritance would allow one object to extend another object's capability instead of having to create another set of functions to do the same things. Today, one has to completely replicate an object to extend it, and that makes for some wasteful code. Inheritance would allow making objects that have small variations in functionality. If one could over-load a given function in an inherited object that would be even better. Constructors, etc... not necessary.

    Wow, prototypal inheritance. Let's morph Spin into JavaScript:)
    http://javascript.crockford.com/prototypal.html
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 21:31
    You can do that without pointers, it's called subclassing. I think that David is craving some of the more essential features of C++. If we can subclass objects, then we can make objects that do I/O, then combine that with a specific implementation of a function.

    PHP implements subclassing in it's object model, but it doesn't implement multiple-inheritance. With multiple-inheritance you can subclass an I/O class and a numbers class and string class, but then substitute your own implementations of certain functions or swap in from different objects.

    Basically it's like an upside down tree, where multiple classes can come together to form one class. You can then overload functions or declare your own code with the same calling parameters.

    This all gets very messy very quick, but there can be some strict rules to help make it simpler. I will refrain from giving advice on those exactly, since there are at least 2 people on this forum that are much more capable than me (David and Eric).
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 21:33
    Heater. wrote: »
    Jazzed,



    Wow, prototypal inheritance. Let's morph Spin into JavaScript:)
    http://javascript.crockford.com/prototypal.html

    That's a little unfair, he is only asking for inheritance, not all the crazy stuff JavaScript has evolved into.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 21:38
    David Betz wrote:
    It might be nice to be able to pass an object reference into generic implementations of those functions to do the low-level I/O. That way the same formatted output object could be used for all terminal-like output objects. I guess the same could be done by using inheritance if that were available.

    Yes! (See my post #7.) Although, I would prefer to pass a reference to an individual character output method, rather than to the object itself. That eliminates problems when one object uses out and another uses tx, for example. The syntax could be really simple and intuitive, too:
    OBJ
    
      sio : "FullDuplexSerial"
      prt : "PrintFormatter"
    
    PUB start
    
      sio.start(31, 30, 9600, 0)
      prt.hex(@sio.tx, 123456, 8)
    

    'No special function or object type required. It's up to the programmer, as in Spin I, to do the right thing. And forget about classes, inheritance, and polymorphisms.

    Chip, please make the backend as complicated as it has to be -- even if it means an extra compiler pass -- in order to keep the frontend simple and intuitive.

    Thanks,
    -Phil
  • cgraceycgracey Posts: 14,133
    edited 2013-05-10 21:42
    ...Chip, please make the backend as complicated as it has to be in order to keep the frontend simple and intuitive.

    I want to make them BOTH simple, you know.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 21:44
    cgracey wrote:
    I want to make them BOTH simple, you know.
    Yes, but, but, there's only one of you and thousands -- nay, millions -- of us! :)

    -Phil
  • David BetzDavid Betz Posts: 14,511
    edited 2013-05-10 21:50
    cgracey wrote: »
    So, it would be good if an object's functions could be called, but have a mechanism for those functions to call back to the calling object for I/O operations, for example?

    I think that would mean instantiating an object from within your object, then handing it a pointer, so that it could call back to your object for some required purpose. That would come down to a function pointer, wouldn't it? I mean a function pointer that sets up the context (PBASE/VBASE) as well as the PUB/PRI number for that object. In contrast, an object pointer would contain just the PBASE/VBASE, but not a specific PUB/PRI, as that would be selectable. But then how would the selection be accounted for, being just an index. I think the index built in would be more useful, don't you?
    I must be overtired because I'm not sure I understand your question. In any case I was thinking about something like this:
    OBJ
      myformatter : "TextFormatter"
      myterminal : "VGA_Terminal"
    
    PUB main
      myformatter.setOutput(@myterminal)
      myformatter.dec(1234)
      myformatter.hex(2222, 4)
    

    Then, inside of the implementation of TextFormatter.dec you would see code like:
    VAR
      objref outputObject
    
    PUB setOutput(outputObj)
      outputObject := outputObj
    
    PUB dec(value) | ch
      ' do stuff to assign something to ch
      outputObject.out(ch)
    

    And VGA_Terminal would define a PUB called "out" that would handle the terminal-specific character output.

    As I said originally, this could also be done (probably more cleanly) by having VGA_Terminal inherit from TextFormatter.
  • David BetzDavid Betz Posts: 14,511
    edited 2013-05-10 21:53
    Yes! (See my post #7.) Although, I would prefer to pass a reference to an individual character output method, rather than to the object itself. That eliminates problems when one object uses out and another uses tx, for example. The syntax could be really simple and intuitive, too:
    OBJ
    
      sio : "FullDuplexSerial"
      prt : "PrintFormatter"
    
    PUB start
    
      sio.start(31, 30, 9600, 0)
      prt.hex(@sio.tx, 123456, 8)
    

    'No special function or object type required. It's up to the programmer, as in Spin I, to do the right thing. And forget about classes, inheritance, and polymorphisms.

    Chip, please make the backend as complicated as it has to be -- even if it means an extra compiler pass -- in order to keep the frontend simple and intuitive.

    Thanks,
    -Phil
    One problem I see with passing just the function reference is that it wouldn't allow you to write an object that is a text editor that uses multiple methods from the "terminal" object. For example, tx and rx.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 22:00
    I would argue that it's better to have a standardized calling interface, then your code works more like this:

    prt.hex(@sio, 123456)

    Then you have strongly identified interfaces. The C++ STL abstracts this away somewhat with the iostream class. You can implement instances of the << and >> operators and the objects can communicate with streams, without actually requiring some underlying system be stream oriented, it's just an interface.

    With inheritance you would define a standard object such as io.spin, then that object is inherited by FullDuplexSerial, which re-implements functions that match the defined calling interface.

    Then Formatter can take any object that is derived from io.spin as a parameter:

    PUB hex (io, number)
    'do something
    io.write(stringvalue)

    Furthermore, you might do something like this if anonymous locally scoped objects were supported:

    PUB hex (io, number) | num:numbers.spin
    io.write(num.hex(number))

    It just occurred to me that polymorphism would be a tough one to get right because SPIN doesn't have variable typing in function calls. Because parameters are not typed, there is no value in overloading functions. The only time this might be useful is situations where you have multiple functions of the same name, but different number of parameters.

    None of this is super exotic, it just requires logic in the compiler to pick the correct functions at compile time. C++ is largely deterministic at compile time, with the exception of virtual methods.
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 22:02
    Isn't the problem with just having a function reference the fact that the function is actually a method of an object and that object has state.
    So calling calling such function through a simple pointer means it has no idea which instances state it should be using. It could only uses it's own local variables.
    Not much use is it?

    So, for example, if I have three instances of FDS and pass a pointer to the "tx" method to someone, when that tx is called it has no idea what serial port it applies to.

    This language design business is a bit hairy.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 22:04
    David Betz wrote: »
    I must be overtired because I'm not sure I understand your question. In any case I was thinking about something like this:
    OBJ
      myformatter : "TextFormatter"
      myterminal : "VGA_Terminal"
    
    PUB main
      myformatter.setOutput(@myterminal)
      myformatter.dec(1234)
      myformatter.hex(2222, 4)
    

    Then, inside of the implementation of TextFormatter.dec you would see code like:
    VAR
      objref outputObject
    
    PUB setOutput(outputObj)
      outputObject := outputObj
    
    PUB dec(value) | ch
      ' do stuff to assign something to ch
      outputObject.out(ch)
    

    And VGA_Terminal would define a PUB called "out" that would handle the terminal-specific character output.

    As I said originally, this could also be done (probably more cleanly) by having VGA_Terminal inherit from TextFormatter.

    Thumbs up, agree 100%
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 22:06
    Heater. wrote: »

    This language design business is a bit hairy.

    LOL, it will just end up driving us to C++ and making the syntax look as much like SPIN as possible with pre-processor macros! :lol:
  • Heater.Heater. Posts: 21,230
    edited 2013-05-10 22:06
    pedward,
    I would argue that it's better to have a standardized calling interface, then your code works more like this:
    prt.hex(@sio, 123456)
    I agree.

    Of course in that case we don't need the @, the name of the object should suffice. Just like the name of any other variable.

    Then I'd want to do something like:

    prt.setStream(sio)
    prt.hex(123456)
  • David BetzDavid Betz Posts: 14,511
    edited 2013-05-10 22:06
    Ummm... I hate to mention this but aren't pointers one of the features of C that people find difficult to understand? If so, are we in the process of ruining Spin by adding that feature? In fact, unless we make pointers 64 bits it sounds like their semantics will be worse than C++ since they will be restricted in what object context they can reference. In fact, as Heater pointed out, they won't really be able to access *any* object context. It seems like inheritance would be easier for people to understand. It won't solve every problem but it will make the language more powerful without adding obscure semantics that may take away from the simplicity of Spin.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 22:13
    Heater. wrote: »
    pedward,


    I agree.

    Of course in that case we don't need the @, the name of the object should suffice. Just like the name of any other variable.

    Then I'd want to do something like:

    prt.setStream(sio)
    prt.hex(123456)

    Something doesn't sit right with me about the procedural syntax for passing a handle to the object. Alas, you can't really do it any other way without really tearing apart the language.

    The most elegant way would be to have a constructor that is called at object instantiation time, supporting bot static objects like SPIN 1, but also supporting a syntax like:
    OBJ
    
      fds : "FullDuplexSerial"
    
    VAR
      printobject object
    
    PUB foo
    
      printobject := Formatter(fds)
    
    

    Yes, you can do exactly the same thing using the procedural model, but it would be nice to have a single statement that has parity with other languages, though I suddenly realize there is too much ambiguity in SPIN to permit the latter syntax, so just forget my suggestion.
  • pedwardpedward Posts: 1,642
    edited 2013-05-10 22:16
    David Betz wrote: »
    Ummm... I hate to mention this but aren't pointers one of the features of C that people find difficult to understand? If so, are we in the process of ruining Spin by adding that feature? In fact, unless we make pointers 64 bits it sounds like their semantics will be worse than C++ since they will be restricted in what object context they can reference. In fact, as Heater pointed out, they won't really be able to access *any* object context. It seems like inheritance would be easier for people to understand. It won't solve every problem but it will make the language more powerful without adding obscure semantics that may take away from the simplicity of Spin.

    Agreed, having a pointer to a function, without any context, is only mildly useful and I'm sure there are other clever ways to do the same thing without having it as a mainstream language capability.

    I would argue for trying to maintain an immediate syntax as much as possible, that way we are passing around objects, an @ reference in the call is ~okay~, but the compiler should be able to infer that anyway, since it knows the variable is an object.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2013-05-10 22:20
    heater wrote:
    Isn't the problem with just having a function reference the fact that the function is actually a method of an object and that object has state.
    So calling calling such function through a simple pointer means it has no idea which instances state it should be using.
    That's a good point. However, in my example above, could not the pointer to sio.tx include an instance reference? After all, the moniker "sio" does refer to a specific instance, not to the FullDuplexSerial class in general.

    -Phil
  • David BetzDavid Betz Posts: 14,511
    edited 2013-05-10 22:24
    That's a good point. However, in my example above, could not the pointer to sio.tx include an instance reference? After all, the moniker "sio" does refer to a specific instance, not to the FullDuplexSerial class in general.

    -Phil
    I think Chip was trying to avoid 64 bit pointers.
  • David BetzDavid Betz Posts: 14,511
    edited 2013-05-10 22:28
    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.
Sign In or Register to comment.