Generic Functions
A function is called a generic function if it declares one or more type parameters. Syntactically, type parameters are placed immediately after the function name, enclosed in <>, and separated by , if there are multiple type parameters.
Global Generic Functions
When declaring a global generic function, simply declare the type parameters using angle brackets after the function name. These type parameters can then be referenced in the function parameters, return type, and function body. For example, the id function is defined as:
Here, (a: T) is the function parameter declaration, which uses the type parameter T declared by the id function, and the return type of id also utilizes this type parameter.
Another more complex example is the generic function composition, which declares three type parameters T1, T2, T3. Its functionality is to compose two functions f: (T1) -> T2 and g: (T2) -> T3 into a function of type (T1) -> T3.
Since the functions that can be composed can be of any type (e.g., composition of (Int32) -> Bool and (Bool) -> Int64, or (Int64) -> Rune and (Rune) -> Int8), generic functions are necessary.
Here, composing two (Int64) -> Int64 functions first multiplies 9 by 2 and then adds 10, resulting in 28.
func id<T>(a: T): T {
return a
}func composition<T1, T2, T3>(f: (T1) -> T2, g: (T2) -> T3): (T1) -> T3 {
return {x: T1 => g(f(x))}
}func times2(a: Int64): Int64 {
return a * 2
}
func plus10(a: Int64): Int64 {
return a + 10
}
func times2plus10(a: Int64) {
return composition<Int64, Int64, Int64>(times2, plus10)(a)
}
main() {
println(times2plus10(9))
}28Local Generic Functions
Local functions can also be generic. For example, the generic function id can be nested within other functions:
Due to the identity property of id, the functions id and double ~> id are equivalent, resulting in true.
func foo(a: Int64) {
func id<T>(a: T): T { a }
func double(a: Int64): Int64 { a + a }
return (id<Int64> ~> double)(a) == (double ~> id<Int64>)(a)
}
main() {
println(foo(1))
}trueGeneric Member Functions
Member functions of classes, structs, and enums can be generic. For example:
The program output will be:
When extending types using the extend declaration, the functions within the extension can also be generic. For example, we can add a generic member function to the Int64 type:
The program output will be:
class A {
func foo<T>(a: T): Unit where T <: ToString {
println("${a}")
}
}
struct B {
func bar<T>(a: T): Unit where T <: ToString {
println("${a}")
}
}
enum C {
| X | Y
func coo<T>(a: T): Unit where T <: ToString {
println("${a}")
}
}
main() {
var a = A()
var b = B()
var c = C.X
a.foo<Int64>(10)
b.bar<String>("abc")
c.coo<Bool>(false)
}10
abc
falseextend Int64 {
func printIntAndArg<T>(a: T) where T <: ToString {
println(this)
println("${a}")
}
}
main() {
var a: Int64 = 12
a.printIntAndArg<String>("twelve")
}12
twelveStatic Generic Functions
Interfaces, classes, structs, enums, and extensions can define static generic functions. For example, the following ToPair class returns a tuple from an ArrayList:
import std.collection.ArrayList
class ToPair {
public static func fromArray<T>(l: ArrayList<T>): (T, T) {
return (l[0], l[1])
}
}
main() {
var res: ArrayList<Int64> = ArrayList([1,2,3,4])
var a: (Int64, Int64) = ToPair.fromArray<Int64>(res)
}