A function gives a name to an operation and returns a result. A subroutine performs work without a successful result. Both let you divide a program into pieces you can understand and reuse.
Define an operation
PRINT Area(3.0, 4.0)
ShowGreeting("Ada")
FUNCTION Area(width AS DOUBLE, height AS DOUBLE) AS DOUBLE
RETURN width * height
END FUNCTION
SUB ShowGreeting(name AS STRING)
PRINT "Hello, " + name
END SUBParameters name the inputs. AS DOUBLE after the parameter list names the
function’s successful result type. Every reachable successful path in a FUNCTION
must return a value of that type. A SUB can reach END SUB or use bare RETURN;
EXIT SUB also returns successfully. A function cannot use bare RETURN or
EXIT FUNCTION. Checked failure is a separate exit path, explained in chapter 11.
An expression-bodied function is convenient for a short calculation:
PRINT Square(5)
FUNCTION Square(value AS INTEGER) AS INTEGER = value * valueDefaults and named arguments
PRINT Greeting("Ada")
PRINT Greeting(prefix="Welcome, ", name="Grace")
FUNCTION Greeting(name AS STRING, prefix AS STRING = "Hello, ") AS STRING
RETURN prefix + name
END FUNCTIONRequired parameters precede optional ones. Defaults use checked constant values and construction recipes, rather than arbitrary runtime calls. Positional arguments precede named arguments; a name cannot be supplied twice. Supplied expressions evaluate once in written order; omitted defaults follow. Naming arguments changes which parameter receives a value, not the order in which supplied expressions execute.
Default records and collections
Defaults can construct records, choices, typed errors, nested lists, empty collections, SOME/NONE and scalar tables. Only omitted arguments construct their defaults, after supplied arguments have evaluated. Names resolve in the function’s declaring module; caller locals cannot change which constructor or constant a default means. Arbitrary function calls and mutable variables are not defaults.
TYPE Config
Limit AS INTEGER = 20
Names AS LIST OF STRING = []
END TYPE
FUNCTION Label(config AS Config = Config { Limit=20, Names=["default"] }) AS STRING
RETURN config.Names[0] + ": " + STR(config.Limit)
END FUNCTION
PRINT Label()
PRINT Label(Config { Limit=42, Names=["custom"] })Use an ordinary default when omission means a known configuration. Use an optional when absence is itself meaningful or a fallback must run ordinary code. BYVAL defaults create independent mutable owners on each call:
FUNCTION ExpandedSize(BYVAL values AS LIST OF INTEGER = [1, 2]) AS INTEGER
Append(BYREF values, 3)
RETURN LEN(values)
END FUNCTION
PRINT ExpandedSize()
PRINT ExpandedSize()Both calls print 3. There is no persistent shared default list. Fresh resource literal defaults are constructed and cleaned normally, without copying an existing owner. BYREF and TAKE defaults remain rejected. Task entries use the same checked recipes. Generic recipes are checked when their concrete omitted value is needed.
Normalize an optional parameter locally
An explicit DIM can reuse the name of an ordinary or BYVAL parameter. The
initializer reads the original parameter; subsequent statements see the new local,
which may have a different type. This is called shadowing. Combined with the
lazy ?? fallback from chapter 6, it gives a convenient configuration pattern:
TYPE Config
Limit AS INTEGER = 20
Names AS LIST OF STRING = []
END TYPE
FUNCTION Run(config AS OPTIONAL OF Config = NONE) AS INTEGER
DIM config = config ?? Config { Limit=20 }
Append(BYREF config.Names, "local")
RETURN config.Limit + LEN(config.Names)
END FUNCTION
given = SOME(Config { Limit=40, Names=["original"] })
PRINT Run()
PRINT Run(given)
IF LET original = given THEN
PRINT LEN(original.Names)
END IFThe local change leaves the caller’s value independent. Leaving a nested block restores the original parameter’s visibility. BYREF and TAKE parameters cannot be shadowed, and duplicate locals in one scope remain errors. Shadowing does not extend the original parameter’s borrowed-return lifetime.
Grant writable access explicitly
Default parameters borrow read-only access. Reading a large list parameter does not automatically deep-copy the list. A writable operation requires BYREF at the declaration and the call site:
count = 10
Increment(BYREF count)
PRINT count
SUB Increment(BYREF value AS INTEGER)
value += 1
END SUBBYREF gives the function exclusive writable access for the call. Passing the same storage through conflicting arguments is rejected. BYVAL creates a writable independent local value instead. Chapter 9 explains when to choose each mode.
Overloads and local helpers
Functions may share a name when their signatures let the compiler choose a unique operation. Numeric conversions are explicit; an INTEGER overload and a DOUBLE overload do not make those types interchangeable. Result context can help resolve some otherwise incomplete calls, but the compiler rejects genuine ambiguity.
A local FUNCTION or SUB keeps a helper inside its lexical block. Declare it before use. It does not capture surrounding local variables; pass needed data as parameters. For a function value that snapshots outer copyable data, use an anonymous lambda rather than a named local declaration. Chapter 13 explains both styles and their different capture rules.
Recursion is supported, but stacks are finite. For data whose depth comes from untrusted input, consider an explicit list of pending work and a checked size limit rather than assuming arbitrarily deep recursive calls will fit.