Gosubs are for neatness, functions are for usefulness.
The main advantage to functions is their ability to be passed parameters and return values. For example if you wanted to write a function that would take two values and add them together, you'd do something like this:
FUNCTION add(value1#, value2#)
sum# = value1# + value2#
ENDFUNCTION sum#
Using that function you could add say, 5 and 7 together, and store the answer in a variable, like this:
Whereas with a subroutine, you couldn't pass it parameters, and you cant return values. To get around this you would have to store the 5 and 7 inside variables accessible to your entire program, as well as the sum#. Sometimes this is fine, but when you get into using the same variable name twice - which is very common - you need to use functions.
I dont know if you understand what variable scopes mean but basically, any variables declared in your main program are accessible to the main program. Any variables declared inside functions are ONLY accessible to those functions, allowing you to use the same variable names in the main program without fear of them overwriting each other.
For example, say you wanted to store the object positions of object number 1 inside your main program code, and in a function you wanted to display a circle at the mouse's x and y position. To do this you might use the variables x#, y#, and z# in your main program for the object's position, however you'd probably also want to use variables called x# and y# for your mouse coordinates as well. If you did this inside a gosub, the two x# and y# variables would overwrite each other, however inside a function they dont affect the main program's variables.
Example:
SYNC ON:SYNC RATE 0:HIDE MOUSE
MAKE OBJECT CUBE 1,10
POSITION CAMERA 0,100,-400
POINT CAMERA 0,0,0
DO
MOVE OBJECT 1, 0.5
TURN OBJECT RIGHT 1,1
x# = OBJECT POSITION X(1)
y# = OBJECT POSITION Y(1)
z# = OBJECT POSITION Z(1)
drawCircle()
TEXT 0,0,"Object Position: ("+ STR$(INT(x#)) +","+ STR$(INT(y#)) + "," + STR$(INT(z#))+")"
SYNC
LOOP
FUNCTION drawCircle()
x# = MOUSEX()
y# = MOUSEY()
CIRCLE x#,y#,5
ENDFUNCTION
The above example uses a function to do what I explained. Now here is an example using a subroutine, notice how the x and y position aren't related to the object's position anymore, but instead the mouse's? This is because the variables aren't local to the subroutine, they're local to the entire program (except for functions), and since subroutines are essentially branches off of your main program, they're altering the x and y variables we're using for object positions.
SYNC ON:SYNC RATE 0:HIDE MOUSE
MAKE OBJECT CUBE 1,10
POSITION CAMERA 0,100,-400
POINT CAMERA 0,0,0
DO
MOVE OBJECT 1, 0.5
TURN OBJECT RIGHT 1,1
x# = OBJECT POSITION X(1)
y# = OBJECT POSITION Y(1)
z# = OBJECT POSITION Z(1)
GOSUB drawCircle
TEXT 0,0,"Object Position: ("+ STR$(INT(x#)) +","+ STR$(INT(y#)) + "," + STR$(INT(z#))+")"
SYNC
LOOP
drawCircle:
x# = MOUSEX()
y# = MOUSEY()
CIRCLE x#,y#,5
RETURN
So, thats the main benefit of functions.