Threadon / Functions

Functions

the building block of every program

Defining a function

A function starts with def, the name, parameters in parentheses, an arrow, and a return type. The line ends with a colon. The body is indented.

def add(a: Int32, b: Int32) -> Int32
    return a + b

Each parameter is a name, a colon, and a type. Parameters are separated by commas. A trailing comma is not allowed.

Call a function by name with arguments in parentheses:

def main() -> Int32
    print(add(2, 3))
    return 0
5

Arguments must match the parameter types exactly. Passing an Int32 where an Int8 is expected is a compile error. There is no implicit conversion between argument types, and no way to overload a function.

Return rules

The return type is required. Every function declares one. There is no function without a return type.

Two rules enforce it:

This function is missing a return on one path and will not compile:

def absolute(x: Int32) -> Int32
    if x < 0:
        return -x
    # no return here, so this won't compile

A function that prints and returns nothing:

def say_hi(name: String) -> NoneType
    print("Hello,")
    print(name)
    return

def main() -> Int32
    say_hi("John")
    return 0
Hello, Joep

The bare return at the end of say_hi is required. Leave it out and the compiler complains that the function doesn't return on all paths.

Recursion

A standard recursion example:

def fact(n: Int32) -> Int32
    if n <= 1:
        return 1
    return n * fact(n - 1)

def main() -> Int32
    print(fact(5))
    return 0
120

Keep recursion depth reasonable. Each call uses stack, and very deep recursion will fail at runtime.

Union types in functions

Functions can accept union types as parameters and return them. A union lets one function handle several concrete types without writing separate overloads.

def twice(x: Int | Float) -> Int | Float
    return x * 2

def main() -> Int32
    print(twice(3))
    print(twice(4.5))
    return 0
6 9.000000

The compiler guarantees that every operation inside the function body is valid for all types in the union. That's the tradeoff: you get flexibility, but you can't do anything type-specific inside the function without narrowing first.

For the full picture — narrowing, control flow reversion, group types, and all the gotchas — see Unions.

The entry point

A program starts at a function named main. Its return value becomes the process exit code. Return 0 for success, anything else for failure. That's why every main in these docs ends with return 0.

You don't have to use main. The -e flag picks a different function as the entry point:

python3 -m compiler.main --run -e go file.th

The chosen function's return value becomes the exit code. If it returns NoneType, the exit code is 0. There are two conditions: the function must exist, and the file must not also define main.

See Command line for the details.

Definition order

A function must be defined before it is called. The compiler reads the file top to bottom. Put helper functions above main. The one exception is the standard library, which is loaded before your file starts.