Both subroutines and functions (subroutines that return values) are collectively referred as ‘procedures’.  In a procedural language (such as Rapture), it is beneficial to break down computing tasks into smaller pieces and to reuse bits with related functionality.  By allowing code to be parameterized and reused, the work can be made much more legible and easier to understand.  This logical breakdown of tasks is accomplished by using subroutines and functions (we will collectively call them procedures).  Subroutines and functions are similar in that they operate on data, but of the two, only a function returns a value.  They are declared in the top level of a file (you cannot declare and/or define them inside another block of code), and accessible only within that file (unless a prototype is also provided).

Both subroutines and functions (subroutines that return values) are collectively referred as ‘procedures’.
Before a procedure can be used, it must be declared.
Verbs are special forms of subroutines.
Yet another form of special subroutine.
Parameters are the values that are given to a procedure so that some operation can be done based on their value.
Polymorphism is the ability of something to appear as something else depending on the context in which it is used.

Procedure Headers (and Prototypes)

Before a procedure can be used, it must be declared.  The declaration tells Rapture what sort of parameters the procedure takes, and what type of data (if any) is returned as a result of calling the procedure.  To declare either a subroutine or a function, simply use the appropriate keyword followed by the name of the routine, a formal parameter list enclosed in parentheses, and finally terminated with either the definition of the procedure (a block of code).  For example:

(* A subroutine that returns nothing and takes nothing. *)
subroutine foo();

(* A function returning an integer. This one has a definition. *)
function foo()
return 10;

(* A subroutine taking two integer parameters. *)
subroutine foo(num1, num2);

You can see in the above examples that there are two forms terminated with semicolons.  This is a valid declaration, however there is no actual code associated with them.  They don’t actually do anything, and you’ll need to define them somewhere and eventually have them linked or compiled into your current bit of code.  The form with the semicolon is called a prototype.


Verbs are special forms of subroutines.  They provide a method of mapping string names to a single subroutine definition and then calling them indirectly via the doverb() internal subroutine.  To declare a verb (there is no prototype or external declaration for verbs, they are always global), simply follow the verb keyword by a comma separated list of “verb names”, and finally the definition statement block.

verb "l", "look", "v(iew)"

You can see in the third name, that “iew” is in parentheses.  This indicates those letters are optional, and “v”, “vi”, “vie”, and “view” will all be valid verb names.  Each name listed in the declaration is inserted into an internal lookup data structure at runtime so that an identifying integer may be found.  The <search_verb>() internal is used to map a name to an identifying number.  That number can then be passed to <doverb>() in order to call it.


Yet another form of special subroutine.  These special routines are the only ones that may be scheduled to run at some time in the future.  They are named, managed, mapped, and called all internally to Rapture, all the developer needs to do is declare it, then eventually schedule it to execute via a <game_task>() internal call (or something similar).  The format for a task declaration is the task keyword, followed immediately by the task name (in quotes), and then the definition block.

task "mobile_walk"

Also, in being able to schedule tasks, often, they are scheduled via groups (there are a few different methods of scheduling tasks) related to a given database.  Tasks are automatically associated with a given database record via a parameter passed to the scheduling routine.  (See: <game.task_object>) When that record is deleted or somehow erased, any associated tasks are automatically unscheduled.

While scheduling is the preferred task usage, tasks can also be called directly, using the dotask routine.

dotask("name", taskobject);

The first dotask parameter contains the name to call, the second one will be available inside the task as game.taskobject.


Parameters are the values that are given to a procedure so that some operation can be done based on their value.  The values listed in a procedure header are called formal parameters and serve two functions.  First, they tell Rapture the number and type of values to expect, as well as the order to expect them.  When making a call to the procedure, the caller must provide a value for each parameter, in the order they were defined.  It’s pretty simple really.  For instance, the subroutine declared as:

subroutine foo(num1, num2);

Can be called several ways (this is by no means all of them).

foo(1, 2);
foo(1, x);
foo(5 + x, 10);

Each parameter is passed by value which means that the value is copied into the local variable declared in the header.  For the second example in the above, the value of 1 is copied into the parameter num1 and the value of x is copied into num2.  Any changes made to num1 and num2 in the procedure body of foo() will not affect the original values in the calling procedure.


Polymorphism is the ability of something to appear as something else depending on the context in which it is used.  In RPL it is much the same.  Procedures in RPL permit this by looking at the parameters provided and return value expected (if any) and choosing between possibly many routines with the same name.  For instance, a function called combine() could be declared in two different ways without conflicting:

function combine(num1, num2);
function combine(string1$, string2$);

These two procedures, despite having the same ‘name’ are quite different.  In the first, the parameters are two integers, and in the second there are two strings.  Now, let’s say that each combine() function simply adds the two parameters and returns the integer summation.  In the first, it is a direct addition, but in the second, perhaps it converts the strings to integers first before adding them.

Indeed, the functionality (no pun intended) of each procedure doesn’t have to be related at all.  The first combine() function could do a summation and the second could return the price of a cheeseburger after adding extra mayo and pickles.  However, this is usually bad programming practice, as the name of a procedure is usually intended to give some in hint as to its purpose.

If the two purposes are different, it leads to confusion in development.  So, for instance, you could call them like the following (note that two different procedures are being called):

x = combine(5, 10);       (* x gets 15 *)
x = combine("5", "10"); (* x gets 15 *)

Also note that the polymorphism described here is not the same as OO polymorphism and dynamic binding of procedure calls.  Rapture has compile-time context polymorphism only.