A couple of things pertaining to functions

Started by bmorris, March 29, 2013, 10:21:28 AM

Previous topic - Next topic

bmorris

I've been reading about Functions and PSubs, and I'm wondering if there are subtleties about the distinction that I don't know about. My understanding is that they're the same except that PSubs: can't be exited from early; have their variables as static by default; and are slightly faster than Functions. I can imagine that the speed difference could count in programs with intensive calculations etc, so is the ability to exit early costly too somehow?

And regarding arrays, given that they're global (a fact I was pleasantly surprised to discover recently, having assumed they were local by default) when is it truly necessary to pass them to functions as arguments? The only situation I can imagine is one where you want to use one in a function with the same name as a global one, but there, you can just make it local.

kevin

#1
 @ bmorris,

QuoteI've been reading about Functions and PSubs, and I'm wondering if there are subtleties about the distinction that I don't know about. My understanding is that they're the same except that PSubs: can't be exited from early; have their variables as static by default; and are slightly faster than Functions.

  The older parts of the documentation like the functions tutorial tend to be pretty superficial.    The major difference between them, is that when we call a function, all of the locals are allocated on the stack and initialized when we enter it.  Generally the more locals within a function, the slower it is to initialize and the greater the impact upon performance.    The benefit is that this allows functions to be called recursively though.  Psubs don't support this at all.  A PSUB is nothing more than  a GOSUB/RETURN structure that PB has wrapped up for us.

  So a PSUB like this,

PlayBASIC Code: [Select]
   A=AddThings(10,20)

print a
sync
waitkey


Psub AddThings(B,C)
result = B+C
endpsub result



   Is virtually the same as this,

PlayBASIC Code: [Select]
   B=10
C=20
Gosub AddThings
A=Result

print a

sync
waitkey

AddThings:
result = B+C
return




  So PSUBS are indeed simply Protected subroutines, protected in that the compiler emulates 'locals' and hard codes the  calling and returning code into your program for you.  So it looks and feels like a function, but it isn't.     You can EXIT them safely using RETURN, but only if the psub doesn't return anything.  It's generally a good idea to try and make function/sub code adhere to one entry and one exit policy.    



Quote
I can imagine that the speed difference could count in programs with intensive calculations etc, so is the ability to exit early costly too somehow?

   When a function has ended by either ExitFUNCTION or EndFUNCTION, the behavior is the same.   It sets the return values and releases it's current scope from the stack.   So conceptually all the stuff that was allocated when it was called, is flushed out.    I say conceptually, as that's how it appears to the end user, but internally it attempts to preserve instance data between calls, for performance reasons.  

   In terms of speed, I generally use PSUBS for small routine, where I'm assuming the code will be called frequently and FUNCTIONS for anything else.


Quote
And regarding arrays, given that they're global (a fact I was pleasantly surprised to discover recently, having assumed they were local by default) when is it truly necessary to pass them to functions as arguments? The only situation I can imagine is one where you want to use one in a function with the same name as a global one, but there, you can just make it local.

   Most programs tend to be written around a hand full of common global data structures.   So we'd declare some typed array of game character things or whatever, then set up a function interface to abstract it from the game code.   The functions would then all operate upon whatever index(s) we pass them.        

PlayBASIC Code: [Select]
    Type GameCharacter
x#,y#
Sprite
etc etc
EndType

DIm Objects(0) as GameCharacter


Psub NewCharacter(X#,y#)

Index=GetFreeCells(Object())

Objects(Index) = New GameCharacter

Objects(Index) .X=X#
Objects(Index) .y=y#

EndPsub Index

Psub CharactersHit(ThisDude,ThatDude,Radius=10)
result =false
if Objects(ThisDude)
if Objects(ThatDude)

x1#=Objects(ThisDude) .X
y1#=Objects(ThisDude) .Y

x2#=Objects(ThatDude) .X
y2#=Objects(ThatDude) .Y
result= (Radius <=GetDistance2D(x1#,y1#,x2#,y2#))

endif
endif
EndPsub

Player=NewCharacter(100,200)

BadGuy=NewCharacter(400,400)

print CharactersHit(PLayer,BadGuy,1000)

etc etc




   
    So under this type of model, we don't need to pass the entire data structure around in memory, the index of the thing we want to query/calculate with is more than enough.

    Passing arrays in is handy when we have a number of arrays of the same type and want to perform some common calculation upon them.  

    There's bunch of higher level examples sprinkled all over the boards..  
    Managing Arrays - 2D Grids


bmorris

Thanks for the thorough answer. Lots of interesting bits there (e.g. about data being "secretly" retained from the stack from function calls).

kevin

  Internally the PlayBASIC runtime tries to avoid memory thrashing wherever possible.  So things tend to get recycled or cached.   Strings are probably the most obvious benefactor of this approach, but it appears all over the place from images/sprites through to newer optimizer modes like Type Caching.