Procedures & functions¶
Procedures are where a program stops being a transcript and starts becoming a vocabulary. Name the actions with SUB; name the calculations with FUNCTION.
SUB: perform an action¶
DECLARE SUB DrawRule (Width AS INTEGER, Mark AS STRING)
CALL DrawRule(12, "-")
SUB DrawRule (Width AS INTEGER, Mark AS STRING)
FOR I% = 1 TO Width
PRINT Mark;
NEXT I%
PRINT
END SUB
The DECLARE publishes the signature before use. The body may appear later in the file.
Call syntax can be explicit:
or in the compact form:
Use EXIT SUB for an early, intentional return.
FUNCTION: produce a value¶
Assign to the function's own name to set its result:
DECLARE FUNCTION Clamp% (Value AS INTEGER, Low AS INTEGER, High AS INTEGER)
PRINT Clamp%(120, 0, 100)
FUNCTION Clamp% (Value AS INTEGER, Low AS INTEGER, High AS INTEGER)
IF Value < Low THEN
Clamp% = Low
ELSEIF Value > High THEN
Clamp% = High
ELSE
Clamp% = Value
END IF
END FUNCTION
A return type can be expressed by suffix or a trailing AS type. Use EXIT FUNCTION when the result is already assigned and no more work is needed.
BYREF and BYVAL choose what the callee may change¶
Parameters are BYREF by default. If the caller supplies a writable variable, the procedure can change that caller's storage.
DECLARE SUB Nudge (N AS INTEGER)
DIM Score AS INTEGER
Score = 40
CALL Nudge(Score)
PRINT Score ' 41
SUB Nudge (N AS INTEGER)
N = N + 1
END SUB
Write BYVAL when the procedure needs a value but must not rebind the caller:
The extra-parentheses compatibility rule is also preserved:
An ordinary BYREF alias requires an exact-typed writable place. A literal
or calculated expression has no place to alias, so it travels through a
temporary value. A writable variable of a different numeric type is not
silently converted and then presented as a reference; make the value boundary
explicit instead:
DECLARE SUB ShowLong (N AS LONG)
DIM Count AS INTEGER
Count = 7
' CALL ShowLong(Count) ' refused: INTEGER storage is not a LONG place
CALL ShowLong(BYVAL Count) ' converted value; Count cannot be changed
CALL ShowLong((Count)) ' the same forced-value rule
That exact-place rule prevents a callee from appearing to update a caller when it was really changing a hidden conversion temporary.
A good new-code default
Mark read-only inputs BYVAL. Leave BYREF for outputs, in/out parameters, large value records that should not be copied, and APIs whose purpose is mutation.
The important question is not merely “does this copy?” It is what may the callee change that the caller can observe?
| Argument kind | BYVAL |
BYREF |
|---|---|---|
| numeric scalar | receives an independent value | may replace the caller's scalar |
STRING |
may reassign its local parameter only | may replace the caller's string |
TYPE record |
receives an independent record copy | may edit the caller's record in place |
CLASS reference |
cannot rebind the caller, but still refers to the same object's identity | may mutate the object and rebind the caller's reference |
whole array A() |
forbidden | shares the array binding; element changes are visible |
Object parameters deserve a second look. BYVAL protects the caller's
variable, not the object from its own methods:
SUB RenameButton (BYVAL Button AS WorksButton)
Button.SetText "Continue" ' same object: caller observes the new label
Button = NOTHING ' local parameter only: caller stays bound
END SUB
Use a return value when a function naturally produces one thing. Use a clear
BYREF output when a procedure must publish several related results:
SUB DivideWithRemainder (BYVAL Value AS INTEGER, BYVAL By AS INTEGER, BYREF Quotient AS INTEGER, BYREF Remainder AS INTEGER)
Quotient = Value \ By
Remainder = Value MOD By
END SUB
Do not use BYREF merely to save typing. It enlarges the procedure's authority,
so its name and documentation should make mutation unsurprising.
Parameter forms¶
DECLARE SUB Mix (BYVAL Count AS INTEGER, BYREF Total AS LONG, Names() AS STRING, BYVAL Theme AS WorksTheme)
- Scalar parameters name one typed value or reference.
A()names a whole-array parameter; bounds remain available throughLBOUNDandUBOUND.- Records can travel by value or reference.
- Class values are counted object references.
BYVALpasses an alias without allowing the parameter to rebind the caller;BYREFmay rebind it.
WEAK is not a parameter modifier. It is legal only on a CLASS field; parameters, locals, globals, arrays, and TYPE fields are always ordinary values or strong object references.
Whole-array parameters¶
DECLARE SUB ClearScores (Scores() AS INTEGER)
SUB ClearScores (Scores() AS INTEGER)
ARRAYFILL Scores(), 0
END SUB
The empty parentheses are part of the contract: this parameter receives an array binding, not one element. See Arrays.
Scope and storage duration¶
| Form | Visibility | Lifetime |
|---|---|---|
module DIM |
module | program/session |
DIM SHARED |
procedures in the module | program/session |
procedure DIM |
current call | until the call returns |
procedure STATIC |
current procedure | retained between calls |
trailing STATIC procedure |
procedure locals | retained between calls |
DIM SHARED SessionName AS STRING
SUB Tick STATIC
DIM Count AS LONG
Count = Count + 1
PRINT SessionName; Count
END SUB
DEF FN: the compact function¶
For a small expression-only calculation, classic DEF FN remains useful:
DEF FN parameters are values. Move to a full FUNCTION when the calculation needs branches, local state, object members, or a longer explanation.
Records as results¶
A function can return a TYPE value. The caller receives an independent record copy:
TYPE Point
X AS INTEGER
Y AS INTEGER
END TYPE
DECLARE FUNCTION Origin () AS Point
FUNCTION Origin () AS Point
Origin.X = 0
Origin.Y = 0
END FUNCTION
This reads naturally once you remember the central rule: records are values; classes are references.
Object members¶
Class method signatures live inside the CLASS; bodies use qualified names at module scope:
CLASS Counter
PUBLIC:
Value AS INTEGER
DECLARE SUB Add (BYVAL Amount AS INTEGER)
END CLASS
SUB Counter.Add (BYVAL Amount AS INTEGER)
THIS.Value = THIS.Value + Amount
END SUB
Constructors, destructors, properties, access control, and virtual dispatch are taught in Records & Objects.
Design advice from a BASIC teacher¶
- A
SUBshould sound like a verb:PaintButton,LoadPage,AdvanceFocus. - A
FUNCTIONshould sound like the value it returns:Clamp,HitItem,Pending. - Keep I/O at the edge. A calculation that does not print, draw, or call an Intent is easier to test and easier for the optimizer to understand.
- Prefer one clear output over a thicket of
BYREFside effects. - Let a procedure earn its parameters. Passing half the program is usually a sign that the data wants to become a record or object.
Next: Arrays →