Functions
Defining, calling, and advanced function features.
Basic Function Definition
Functions are declared with func (or fn shorthand), parameters with types, and a return type. fn can be used anywhere func is used.
func greet(str $name) str {
return "Hello, " . $name . "!";
}
func add(int $a, int $b) int {
return $a + $b;
}
func print_message(str $msg) void {
say($msg);
# No return needed for void functions
}
Calling Functions
my str $greeting = greet("World");
my int $sum = add(10, 20);
print_message("Hello!");
Parameter Types
Scalar Parameters
func process(int $n, str $label, num $factor) void {
say($label . ": " . ($n * $factor));
}
Array Parameters
func sum_array(array @numbers) int {
my int $total = 0;
foreach my int $n (@numbers) {
$total += $n;
}
return $total;
}
my array @nums = [1, 2, 3, 4, 5];
say(sum_array(@nums)); # 15
Hash Parameters
func print_config(hash %config) void {
my array @keys = keys(%config);
foreach my str $key (@keys) {
say($key . ": " . $config{$key});
}
}
Variadic Functions
Functions can accept a variable number of arguments using the ...@param syntax. The variadic parameter must be the last one:
# Basic variadic function
func sum(int ...@nums) int {
my int $total = 0;
foreach my int $n (@nums) {
$total = $total + $n;
}
return $total;
}
sum(1, 2, 3); # 6
sum(10, 20, 30, 40); # 100
# Fixed params + variadic
func format(str $prefix, str $sep, int ...@nums) str {
# $prefix and $sep are fixed, @nums collects remaining args
}
Spread Operator
Use ...@array to unpack an array into individual arguments:
my array @values = (10, 20, 30);
sum(...@values); # 60
sum(1, ...@values, 99); # 160 (mixed args and spread)
Return Types
| Return Type | Description |
|---|---|
void | No return value |
int | Returns integer |
num | Returns floating-point |
str | Returns string |
array | Returns array |
hash | Returns hash |
scalar | Returns any value |
dynamic | Context-sensitive return (use with core::wantarray()) |
Dynamic Return Type
The dynamic return type enables functions to check how their result will be used and return different types accordingly:
func flexible() dynamic {
if (core::wantarray()) {
my array @r = (1, 2, 3);
return @r; # array context
}
if (core::wanthash()) {
my hash %h = ();
$h{"key"} = "val";
return %h; # hash context
}
return 42; # scalar context (default)
}
my array @a = flexible(); # array context
my hash %h = flexible(); # hash context
my int $v = flexible(); # scalar context
Anonymous Functions (Closures)
Strada supports first-class anonymous functions that capture variables from their enclosing scope:
# Create an anonymous function
my scalar $double = func (int $n) {
return $n * 2;
};
# Call with arrow syntax
say($double->(5)); # 10
say($double->(21)); # 42
Capturing Variables
Closures capture variables by reference:
func make_counter() scalar {
my int $count = 0;
return func () {
$count++;
return $count;
};
}
my scalar $counter = make_counter();
say($counter->()); # 1
say($counter->()); # 2
say($counter->()); # 3
Higher-Order Functions
func apply(scalar $fn, int $value) scalar {
return $fn->($value);
}
my scalar $square = func (int $n) { return $n * $n; };
my scalar $negate = func (int $n) { return -$n; };
say(apply($square, 5)); # 25
say(apply($negate, 5)); # -5
Closures as Callbacks
Closures without a return statement automatically return undef. This is common when using closures as callbacks:
# Closure used as callback - no return needed
$db->transaction(func () {
$db->execute("UPDATE users SET active = 1");
$db->execute("INSERT INTO logs ...");
# No return - automatically returns undef
});
# Array processing with side effects
foreach my scalar $item (@items) {
$process->($item); # Call closure, ignore return
}
Optional Parameters
Parameters with default values are optional:
func greet(str $name, str $greeting = "Hello") str {
return $greeting . ", " . $name . "!";
}
say(greet("Alice")); # Hello, Alice!
say(greet("Bob", "Hi")); # Hi, Bob!
Variable Arguments
Accept any number of arguments with @args:
func sum(array @numbers) int {
my int $total = 0;
foreach my int $n (@numbers) {
$total += $n;
}
return $total;
}
say(sum(1, 2, 3)); # 6
say(sum(1, 2, 3, 4, 5)); # 15
Function References
Get a reference to a named function with \&funcname:
func double(int $n) int {
return $n * 2;
}
my scalar $ref = \&double;
say($ref->(10)); # 20
Calling Functions in Current Package
When working inside a package, use ::func() to call other functions in the same package without repeating the package name:
package Calculator;
func add(int $a, int $b) int {
return $a + $b;
}
func multiply(int $a, int $b) int {
return $a * $b;
}
func compute(int $x, int $y) int {
my int $sum = ::add($x, $y); # Calls Calculator_add
my int $prod = ::multiply($x, $y); # Calls Calculator_multiply
return $sum + $prod;
}
::func()— Preferred shorthand.::func()— Alternate shorthand__PACKAGE__::func()— Explicit form
All three resolve to PackageName_func() at compile time.
Recursion
func factorial(int $n) int {
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}
say(factorial(5)); # 120
func fibonacci(int $n) int {
if ($n <= 1) {
return $n;
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
External Functions (C Interop)
Declare external C functions with extern:
# Declare external C function
extern func my_c_function(int $arg) int;
# Call it like any other function
my int $result = my_c_function(42);
Signal Handlers
Functions can be used as signal handlers:
func handle_sigint() void {
say("Caught SIGINT!");
core::exit(0);
}
# Register the handler
core::signal("INT", \&handle_sigint);
# Or ignore signals
core::signal("PIPE", "IGNORE");
UTF-8 Introspection (utf8::)
The utf8:: namespace provides Perl-compatible UTF-8 introspection functions. In Strada, all strings are stored as raw bytes (typically UTF-8). There is no separate "UTF-8 flag" like Perl has.
| Function | Description |
|---|---|
utf8::is_utf8($str) | Returns 1 if string is valid UTF-8, 0 otherwise |
utf8::valid($str) | Alias for is_utf8() |
utf8::encode($str) | No-op (strings are already bytes), returns the string |
utf8::decode($str) | Validates UTF-8; returns 1 if valid, 0 if not |
utf8::upgrade($str) | No-op (strings are already UTF-8), returns the string |
utf8::downgrade($str) | Returns string if ASCII-only, dies on non-ASCII |
utf8::downgrade($str, 1) | With fail_ok=1, returns undef instead of dying |
utf8::unicode_to_native($cp) | Identity mapping on modern systems |
Example
my str $ascii = "Hello";
say(utf8::is_utf8($ascii)); # 1 (ASCII is valid UTF-8)
say(utf8::downgrade($ascii)); # "Hello" (ASCII-only, succeeds)
# Safe downgrade with fail_ok flag
my scalar $result = utf8::downgrade($ascii, 1);
if (!defined($result)) {
say("Contains non-ASCII characters");
}
Unlike Perl, Strada does not have an internal "UTF-8 flag" on strings. All strings are raw byte sequences. The utf8:: functions validate byte sequences as UTF-8 but do not change internal representation. utf8::encode() and utf8::upgrade() are no-ops provided for Perl compatibility.
Best Practices
- Use specific types when possible,
scalaronly when needed - Keep functions small and focused on one task
- Use descriptive names:
calculate_totalnotcalc - Document complex functions with comments