User-Defined Functions
User-defined functions let you extract reusable, named logic with explicit parameters. They are pure - they cannot access Data or capture variables from the enclosing script.
func CalculateTax(amount: number, rate: number)
return amount * rate
end
Data.tax = CalculateTax(Data.subtotal, Data.taxRate)
Data.total = Data.subtotal + Data.tax
Declaration
A function declaration starts with func, followed by a name, a parameter list in parentheses, a body of statements, and end:
func Name(param1, param2: type)
# body (any statements: var, if, while, for, return, etc.)
end
Functions must be declared at the top level of the script - not inside if blocks, loops, or other functions:
# INCORRECT - function declarations must be at the top level
if Data.mode == "advanced" then
func Helper(x)
return x * 2
end
end
# INCORRECT - functions cannot be nested inside other functions
func Outer()
func Inner()
return 42
end
return Inner()
end
Function names must be unique. A function cannot share a name with a built-in or host-provided function:
func Round(x) # Error: 'Round' is a built-in function
return x
end
Parameters
Parameters are listed by name with optional type hints. When a type hint is present, the argument is coerced to that type when the function is called - the same behaviour as a typed var declaration.
func Add(a: number, b: number)
return a + b
end
func Label(value)
return "Value: " + ToString(value)
end
By default, parameters are required. Every call must supply the expected number of arguments:
func Multiply(a, b)
return a * b
end
Data.result = Multiply(3, 4) # 12
Data.result = Multiply(3) # Error: expected 2 arguments, got 1
Data.result = Multiply(3, 4, 5) # Error: expected 2 arguments, got 3
Parameter names must be unique within a function, and Data cannot be used as a parameter name.
Default parameter values
A parameter can be made optional by assigning a default value with =. When the caller omits an optional argument (or passes null), the default value is used instead:
func Greet(name, greeting = "Hello", excited = false)
var msg = greeting + ", " + name
if excited then
msg = msg + "!"
end
return msg
end
Data.a = Greet("Alice", "Hi", true) # "Hi, Alice!"
Data.b = Greet("Bob", "Hey") # "Hey, Bob"
Data.c = Greet("Charlie") # "Hello, Charlie"
Data.d = Greet("Dave", null) # "Hello, Dave" — null triggers default
Default values and type hints can be combined:
func TypedGreet(name: string, greeting: string = "Hello", excited: boolean = false)
var msg = greeting + ", " + name
if excited then
msg = msg + "!"
end
return msg
end
Rules for default parameters:
- Default values must be literals (numbers, strings, booleans, or
null) - not expressions or variable references. - Required parameters must come before optional parameters. Placing a required parameter after an optional one is a compiler error.
- When calling with positional arguments, optional arguments can only be omitted from the right - you cannot skip a middle optional argument.
Return values
Inside a function, return sends a value back to the caller:
func Double(x: number)
return x * 2
end
var result = Double(5) # 10
A bare return without a value returns null:
func MaybeDouble(x)
if x is not number then
return # returns null
end
return x * 2
end
var a = MaybeDouble(5) # 10
var b = MaybeDouble("hi") # null
If the function body reaches end without hitting a return, the function returns null.
return is exclusively for functions. At the script’s top level, use exit for clean termination and fail for error termination - using return outside a function is a compiler error.
Calling functions
Call user-defined functions the same way as built-in functions - by name with arguments in parentheses. Function calls are expressions, so they work anywhere an expression is valid:
Data.tax = CalculateTax(Data.subtotal, Data.taxRate)
if IsValid(Data.amount) then
Data.status = "ok"
end
var valid = Where(Data.items, item => IsValid(item.price))
var total = Reduce(Data.values, (sum, v) => sum + Double(v), 0)
Named arguments
Instead of passing arguments by position, you can pass them by name using paramName: value syntax. Named arguments can appear in any order:
func Greet(name, greeting = "Hello", excited = false)
var msg = greeting + ", " + name
if excited then
msg = msg + "!"
end
return msg
end
# Arguments in any order
Data.a = Greet(excited: true, name: "Alice", greeting: "Hi") # "Hi, Alice!"
# Omit optional arguments freely
Data.b = Greet(name: "Bob") # "Hello, Bob"
# Skip middle optional arguments
Data.c = Greet(name: "Charlie", excited: true) # "Hello, Charlie!"
# Provide all arguments
Data.d = Greet(name: "Eve", greeting: "Hey", excited: false) # "Hey, Eve"
Named arguments work with standard library functions too:
# Substring(text, start, length?) — reorder freely
Data.sub = Substring(start: 2, text: "Hello World")
# PadLeft(text, length, padChar?) — skip optional padChar
Data.pad = PadLeft(text: "hi", length: 8)
# Replace(source, oldValue, newValue) — fully reordered
Data.replaced = Replace(newValue: "World", oldValue: "X", source: "Hello X")
Rules for named arguments:
- A call must use either all positional arguments or all named arguments - you cannot mix the two styles in a single call.
- Every required parameter must be provided. Optional parameters with defaults can be omitted.
- Each parameter name can only appear once per call.
- The parameter name must match an actual parameter of the function being called.
Function hoisting
Functions can be called before they are declared. All function declarations are visible throughout the entire script, regardless of where they appear in the source:
Data.result = Process(Data.input)
func Process(x)
return x * 2
end
Functions can call each other freely (mutual recursion):
func IsEven(n: number)
if n == 0 then return true end
return IsOdd(n - 1)
end
func IsOdd(n: number)
if n == 0 then return false end
return IsEven(n - 1)
end
Data.result = IsEven(4) # true
Hoisting applies only to function declarations. Variables declared with var are not hoisted - they exist from the point of declaration forward.
No Data inside functions
Functions cannot access Data. This is the most important rule about user-defined functions, and the compiler enforces it:
func Bad()
return Data.value # Error: cannot access Data inside a function
end
If a function needs information from Data, pass it as an argument:
func Good(value)
return value * 2
end
Data.result = Good(Data.value)
The rule keeps functions pure. A function’s behaviour depends only on its arguments. The top level of the script is where Data is read and written; functions are helpers that the top level calls.
No closures
Functions cannot see variables from the surrounding script. Each function has its own isolated scope containing only its parameters and any variables it declares locally:
var multiplier = 3
func Scale(x)
return x * multiplier # Error: 'multiplier' is not declared
end
Pass the value as a parameter instead:
func Scale(x, multiplier)
return x * multiplier
end
var m = 3
Data.result = Scale(Data.value, m)
This is where functions and lambdas differ. Lambdas capture variables from the surrounding scope:
var multiplier = 3
var scaled = Map(Data.values, x => x * multiplier) # this works
Functions enforce an explicit interface - every dependency comes through the parameter list. Lambdas are lightweight inline expressions that close over their environment. See Lambda Expressions for more detail.
exit and fail inside functions
exit terminates the entire script, even from inside a function. It does not just return from the function - it stops everything:
func Validate(x)
if x is null then
exit "missing required value" # terminates the script
end
return x * 2
end
fail works the same way - it terminates the script with an error, regardless of how deep in the call stack it appears:
func RequirePositive(x, name)
if x is not number then
fail name + " must be a number"
end
if x <= 0 then
fail name + " must be positive"
end
return x
end
var amount = RequirePositive(Data.amount, "amount")
var rate = RequirePositive(Data.rate, "rate")
Data.result = amount * rate
The three keywords have clear, non-overlapping meanings:
| Keyword | Effect | Valid where |
|---|---|---|
return | Exit the function, resume at the call site | Inside functions only |
exit | Terminate the script cleanly (success) | Anywhere |
fail | Terminate the script with an error (failure) | Anywhere |
Recursion
A function can call itself. Jyro enforces a maximum call depth to prevent infinite recursion from exhausting memory:
func Factorial(n: number)
if n <= 1 then
return 1
end
return n * Factorial(n - 1)
end
Data.result = Factorial(5) # 120
Examples
Validation helpers
func RequireString(value, fieldName)
if value is not string or value == "" then
fail fieldName + " is required"
end
return value
end
func RequireNumber(value, fieldName, min, max)
if value is not number then
fail fieldName + " must be a number"
end
if value < min or value > max then
fail fieldName + " must be between " + ToString(min) + " and " + ToString(max)
end
return value
end
var name = RequireString(Data.name, "name")
var age = RequireNumber(Data.age, "age", 0, 150)
Data.record = { "name": name, "age": age }
Data transformation
func FullName(first, last)
return Trim(first) + " " + Trim(last)
end
func TotalWithTax(amount: number, taxRate: number)
return Round(amount + amount * taxRate, 2)
end
foreach order in Data.orders do
order.customerName = FullName(order.firstName, order.lastName)
order.total = TotalWithTax(order.subtotal, Data.taxRate)
end
Composing with lambdas
func IsEligible(customer)
if customer.age < 18 then return false end
if customer.status != "active" then return false end
if customer.balance < 0 then return false end
return true
end
var eligible = Where(Data.customers, c => IsEligible(c))
Data.eligibleCount = Length(eligible)