Functions

Functions are reusable blocks of code that perform specific tasks. They help organize code and avoid repetition.

Function Syntax

Functions are defined using the fn keyword:

fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // function body
    return value
}

Simple Function

// Function with no parameters, no return value
fn greet() {
    print("Hello, World!")
}

// Call the function
greet()

// Function with parameters
fn greet_person(name: string) {
    print("Hello, " + name + "!")
}

greet_person("Alice")  // Hello, Alice!

Functions with Return Values

// Return integer
fn add(a: int, b: int) -> int {
    return a + b
}

let sum = add(5, 3)  // 8

// Return float
fn calculate_area(radius: float) -> float {
    let pi = 3.14159
    return pi * radius * radius
}

let area = calculate_area(5.0)  // 78.53975

// Return boolean
fn is_even(n: int) -> bool {
    return n % 2 == 0
}

let check = is_even(4)  // true

Type Annotations Required: All function parameters must have explicit type annotations. Return types can be inferred but explicit annotation is recommended.

Parameters and Arguments

Parameters are variables listed in the function definition. Arguments are the actual values passed when calling the function.

Multiple Parameters

// Function with multiple parameters
fn calculate_bmi(weight: float, height: float) -> float {
    return weight / (height * height)
}

let bmi = calculate_bmi(70.0, 1.75)  // 22.86

// Different parameter types
fn print_info(name: string, age: int, score: float) {
    print("Name: " + name)
    print("Age: " + str(age))
    print("Score: " + str(score))
}

print_info("Alice", 25, 95.5)

Array Parameters

// Function that takes array
fn sum_array(numbers: [int]) -> int {
    let sum = 0
    let i = 0
    while i < len(numbers) {
        let sum = sum + numbers[i]
        let i = i + 1
    }
    return sum
}

let nums = [1, 2, 3, 4, 5]
let total = sum_array(nums)  // 15

// Function returning array
fn create_range(n: int) -> [int] {
    let result = []
    let i = 0
    while i < n {
        // Note: In practice, array building requires special handling
        let i = i + 1
    }
    return result
}

Tensor Parameters

// Function operating on tensors
fn normalize(x: Tensor) -> Tensor {
    let mean = tensor_mean(x)
    let std = tensor_std(x)
    let centered = tensor_subtract(x, mean)
    return tensor_divide(centered, std)
}

// Neural network layer
fn linear_layer(input: Tensor, weights: Tensor, bias: Tensor) -> Tensor {
    let output = nn_linear(input, weights, bias)
    return output
}

let x = tensor([1.0, 2.0, 3.0])
let normalized = normalize(x)

Return Values

Functions can return values using the return keyword.

Explicit Return Type

// Explicit return type annotation
fn multiply(a: int, b: int) -> int {
    return a * b
}

// Return type can be inferred, but explicit is clearer
fn divide(a: float, b: float) -> float {
    return a / b
}

// Early return
fn absolute_value(x: int) -> int {
    if x < 0 {
        return -x
    }
    return x
}

Returning Multiple Values (Tuples)

// Return tuple for multiple values
fn divide_with_remainder(a: int, b: int) -> (int, int) {
    let quotient = a / b
    let remainder = a % b
    return (quotient, remainder)
}

let result = divide_with_remainder(17, 5)
let quotient = result.0  // 3
let remainder = result.1  // 2

// Statistics function
fn calculate_stats(numbers: [float]) -> (float, float, float) {
    let sum = 0.0
    let i = 0
    while i < len(numbers) {
        let sum = sum + numbers[i]
        let i = i + 1
    }
    let mean = sum / float(len(numbers))
    let min_val = numbers[0]
    let max_val = numbers[0]

    // Find min and max
    let i = 1
    while i < len(numbers) {
        if numbers[i] < min_val {
            let min_val = numbers[i]
        }
        if numbers[i] > max_val {
            let max_val = numbers[i]
        }
        let i = i + 1
    }

    return (mean, min_val, max_val)
}

let data = [1.0, 2.0, 3.0, 4.0, 5.0]
let stats = calculate_stats(data)
print("Mean: " + str(stats.0))
print("Min: " + str(stats.1))
print("Max: " + str(stats.2))

Functions Without Return Values

// Functions that perform actions without returning
fn print_separator() {
    print("====================")
}

fn log_message(level: string, message: string) {
    print("[" + level + "] " + message)
}

log_message("INFO", "Application started")
print_separator()
log_message("ERROR", "Something went wrong")

Type Inference

While parameter types must be explicit, return types can be inferred from the return statement.

// Return type inferred as int
fn square(x: int) {
    return x * x
}

// Return type inferred as float
fn average(a: float, b: float) {
    return (a + b) / 2.0
}

// Return type inferred as bool
fn is_positive(x: int) {
    return x > 0
}

// Explicit return type is still recommended for clarity
fn cube(x: int) -> int {
    return x * x * x
}

Best Practice: Always specify return types explicitly for public functions and library code. Type inference is fine for small helper functions.

Function Scope

Variables declared inside a function are local to that function and cannot be accessed from outside.

fn calculate_something() -> int {
    let local_var = 42
    let result = local_var * 2
    return result
}

let value = calculate_something()
// print(local_var)  // ERROR: local_var is not in scope

// Parameters are also local to the function
fn process(x: int) -> int {
    let x = x + 10  // Shadow parameter
    return x
}

// Global-like pattern using function parameters
fn use_constant() -> float {
    let pi = 3.14159  // Each function has its own scope
    return pi * 2.0
}

Practical Examples

Example 1: Mathematical Functions

// Factorial function
fn factorial(n: int) -> int {
    if n <= 1 {
        return 1
    }
    let result = 1
    let i = 2
    while i <= n {
        let result = result * i
        let i = i + 1
    }
    return result
}

// Power function
fn power(base: float, exponent: int) -> float {
    let result = 1.0
    let i = 0
    while i < exponent {
        let result = result * base
        let i = i + 1
    }
    return result
}

// Greatest common divisor
fn gcd(a: int, b: int) -> int {
    let a = if a < 0 { -a } else { a }
    let b = if b < 0 { -b } else { b }

    while b != 0 {
        let temp = b
        let b = a % b
        let a = temp
    }
    return a
}

print("5! = " + str(factorial(5)))  // 120
print("2^10 = " + str(power(2.0, 10)))  // 1024.0
print("gcd(48, 18) = " + str(gcd(48, 18)))  // 6

Example 2: Array Processing Functions

// Find maximum in array
fn find_max(numbers: [int]) -> int {
    let max_val = numbers[0]
    let i = 1
    while i < len(numbers) {
        if numbers[i] > max_val {
            let max_val = numbers[i]
        }
        let i = i + 1
    }
    return max_val
}

// Find minimum in array
fn find_min(numbers: [int]) -> int {
    let min_val = numbers[0]
    let i = 1
    while i < len(numbers) {
        if numbers[i] < min_val {
            let min_val = numbers[i]
        }
        let i = i + 1
    }
    return min_val
}

// Calculate average
fn calculate_average(numbers: [float]) -> float {
    let sum = 0.0
    let i = 0
    while i < len(numbers) {
        let sum = sum + numbers[i]
        let i = i + 1
    }
    return sum / float(len(numbers))
}

let data = [5, 2, 8, 1, 9, 3]
print("Max: " + str(find_max(data)))  // 9
print("Min: " + str(find_min(data)))  // 1

Example 3: ML Helper Functions

// ReLU activation function
fn relu(x: Tensor) -> Tensor {
    return nn_relu(x)
}

// Sigmoid activation
fn sigmoid(x: Tensor) -> Tensor {
    return nn_sigmoid(x)
}

// Forward pass through simple network
fn forward(input: Tensor, w1: Tensor, b1: Tensor, w2: Tensor, b2: Tensor) -> Tensor {
    // First layer
    let h1 = nn_linear(input, w1, b1)
    let a1 = relu(h1)

    // Output layer
    let output = nn_linear(a1, w2, b2)
    let predictions = sigmoid(output)

    return predictions
}

// Initialize layer weights
fn initialize_weights(input_size: int, output_size: int) -> (Tensor, Tensor) {
    let weights = tensor_randn([input_size, output_size])
    let bias = tensor_zeros([output_size])
    return (weights, bias)
}

// Usage
let w1_b1 = initialize_weights(784, 128)
let w2_b2 = initialize_weights(128, 10)
let input = tensor_randn([1, 784])
let output = forward(input, w1_b1.0, w1_b1.1, w2_b2.0, w2_b2.1)

Example 4: Validation Functions

// Check if tensor shape is valid
fn validate_shape(t: Tensor, expected_dims: int) -> bool {
    let shape = tensor_shape(t)
    return len(shape) == expected_dims
}

// Check if learning rate is valid
fn validate_learning_rate(lr: float) -> bool {
    return lr > 0.0 && lr < 1.0
}

// Validate batch size
fn validate_batch_size(batch_size: int, dataset_size: int) -> bool {
    return batch_size > 0 && batch_size <= dataset_size
}

// Check convergence
fn has_converged(current_loss: float, prev_loss: float, tolerance: float) -> bool {
    let diff = if prev_loss > current_loss {
        prev_loss - current_loss
    } else {
        current_loss - prev_loss
    }
    return diff < tolerance
}

// Usage
let lr = 0.01
if validate_learning_rate(lr) {
    print("Learning rate is valid")
} else {
    print("Invalid learning rate!")
}

Best Practices

DO:

  • Use descriptive function names that describe what they do
  • Keep functions focused on a single task
  • Always annotate parameter types
  • Explicitly specify return types for clarity
  • Use tuples to return multiple values when needed
  • Keep functions short and readable (under 50 lines)
  • Add comments for complex logic

DON'T:

  • Create functions that do too many things
  • Use unclear or abbreviated function names
  • Omit parameter type annotations
  • Create functions with too many parameters (>5)
  • Nest functions deeply
  • Rely on side effects - prefer pure functions when possible

Naming Conventions

  • Functions: Use snake_case for function names
  • Verbs: Start with action verbs: calculate_area, validate_input
  • Boolean functions: Use predicates: is_valid, has_converged
  • Getters: Use get_ prefix: get_size, get_value

Next Steps