Choose a named function, a lambda, or a generic function for the work you need to reuse. The example shows how input and result types connect these forms.
Install Topaz using First Program. If function declarations are new to you, begin with Values and Functions.
Run three function forms
Save this code as functions-generics.tpz in your working folder.
record Label derives Show {
text: string,
}
function applyTwice(value: int, step: (int) -> int) -> int {
step(step(value))
}
function firstOr<T>(values: Array<T>, fallback: T) -> T {
match values {
case [] => fallback
case [first, ..] => first
}
}
function showValue<T: Show>(value: T) -> string {
Show.show(value)
}
let increment: (int) -> int = value => value + 1
let first: string = firstOr(["Topaz", "Classic"], "none")
let shown = showValue(Label { text: "docs" })
print("{applyTwice(3, increment)}")
print(first)
print(shown)In that folder, check the file and run it.
topaz check functions-generics.tpz
topaz run functions-generics.tpzThe check succeeds with exit code 0. The program prints these three lines.
5
Topaz
Label { text: docs }Pass a function as an argument
applyTwice takes an integer and a function, then returns an integer. Its step parameter has type (int) -> int, so the supplied function must accept and return an int.
increment is a lambda, an unnamed function written with =>. It adds one to its input. The call applyTwice(3, increment) computes increment(increment(3)), which gives 5.
The final expression of a function body supplies its result. Use return to leave earlier. A lambda can be stored or passed as a value and can capture surrounding bindings. See Bindings, Scope & Closures for a counter that keeps its value between calls.
Reuse one function with different types
firstOr<T> returns the first array element, or fallback when the array is empty. T is a type parameter. It says that the array elements, fallback, and result have the same type.
The example passes a string array, so Topaz infers T = string and returns "Topaz". Changing that array to [] returns "none". The fallback still supplies the type string.
This is rank-1 polymorphism. Type parameters belong to the named function, and each call determines their types. A lambda receives concrete parameter types from context. Use a named generic function when the same algorithm works with different types, as firstOr does.
Require an operation with a bound
showValue calls Show.show to turn its argument into a string. Show is a static protocol, a named set of operations whose implementation the compiler checks. The bound T: Show requires the argument type to implement it.
Label derives Show supplies that implementation for Label. This is nominal conformance, established for the declared type. A primitive, structural record, function, or collection does not acquire it just by having a similar operation.
Keep T: Show while showValue calls Show.show. Removing the bound makes that call a static error. For a body that uses both protocols, write T: Show + JSON. Both bounds are required, and their order does not matter.
Choose a function form
| What you need | What to use |
|---|---|
| Reusable behavior with known input and result types | A concrete named function |
| Behavior passed or stored as a value | A lambda |
| One algorithm relating inputs and results across types | A named generic function |
| Protocol operations inside a generic body | A named generic function with the required bounds |
Call functions with defaults or extra arguments
Parameters are immutable. A default value is evaluated when its argument is omitted. It must be a literal or const expression and cannot refer to another parameter.
For a variable number of arguments, write the final parameter as ...args: T. Inside the function, args has type Array<T>.
| Call rule | What happens |
|---|---|
| Function and argument evaluation | The callee is evaluated once, then positional and spread arguments from left to right |
| Named arguments | They follow positional and spread arguments |
| Array spread | It supplies the variadic tail and cannot fill fixed parameters |
| Explicit type arguments | Supply the complete list in declaration order, matching the values and expected result |
Generic calls usually infer their type arguments. A partial explicit list is a static error wherever explicit type arguments are supported.
Supported generic declarations
Declare a function's own type parameters and protocol bounds on a named function. Lambdas receive concrete types from their context and cannot declare their own type parameters.
- Lambdas, function types, aliases, nominal declarations, receiver methods, and protocol methods cannot declare bounds.
- A generic body can use the operations permitted by its declared bounds. An operation requiring a stronger bound is a static error.
- Higher-rank polymorphism, generic lambdas, default type parameters, variance annotations, dynamic protocol dispatch, and arbitrary trait constraints are not supported.
Use Types to choose input and result types. Continue to Bindings, Scope & Closures to follow values captured by a function.