Generic Functions

If a function declares one or more type parameters, it is called a generic function. Syntactically, type parameters follow the function name and are enclosed in angle brackets < >. Multiple type parameters are separated by commas ,.

Global Generic Functions

You may declare a global function as generic by using the angle brackets < > enclosing one or more type parameters after the function name. Then you may reference the type parameters in the formal parameters of the function, return type, and function body. For example, the identity function can be defined for any type T as follows:

func id<T>(a: T): T {
    return a
}

In the above definition, <T> is the type parameter of the function, (a: T) is the formal parameter list of the function declaration, in which T is a type variable that corresponds to the type parameter of the id function declaration. The T type variable is then also used as the return type of the id function. Now for any type T there exists a global function id of type (T) -> T that simply returns is parameter.

The next example is more complex. In it, a generic function composition function is defined. That function has three type parameters T1, T2, T3, and is used to compose two functions of types (T1) -> T2 and (T2) -> T3 into one function of the type (T1) -> T3:

func composition<T1, T2, T3>(f: (T1) -> T2, g: (T2) -> T3): (T1) -> T3 {
    return {x: T1 => g(f(x))}
}

Because composition<T1, T2, T3> is a generic function, it may compose two functions of any types, as long as each of them has a single formal parameter and the return value type of the first function is also the type of the sole formal parameter of the other. For instance, (Int32) -> Bool, (Bool) -> Int64 or (Int64) -> Rune, (Rune) -> Int8.

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))
  return 0
}

Here, two (Int64) -> Int64 functions are composed. We multiply 9 by 2 and then add 10 to obtain the result 28.

28

Local Generic Functions

A local function can also be generic. For example, the id generic function can be nested and defined in other functions.

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))
    return 0
}

Due to the unit element property of id, the id<Int64> ~> double and double ~> id<Int64> functions are equivalent, and the result is true.

true

Generic Member Functions

The instance member functions of classes, structs, and enums can be generic. For example:

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)
    return 0
}

The above program outputs the following:

10
abc
false

When a type is extended using an extend declaration, the functions in the extension can also be generic. For example, we can add a generic member function to the Int64 type.

extend Int64 {
    func printIntAndArg<T>(a: T) where T <: ToString {
        println(this)
        println("${a}")
    }
}

main() {
    var a: Int64 = 12
    a.printIntAndArg<String>("twelve")
}

The program outputs the following.

12
twelve

Generic Static Member Functions

Generic static member functions can be defined in interfaces, classes, structs, enums, and extends. For example, in the following ToPair class, a tuple is returned from ArrayList:

import std.collection.*

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)
    return 0
}