Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Functions

Functions in Y allow you to encapsulate reusable code with explicit type signatures. They are first-class values that can be stored in variables, passed as arguments, and returned from other functions.

Function Declaration

The basic syntax for declaring a function:

fn function_name(parameter: Type): ReturnType {
    // function body
}

Simple Functions

fn greet(): void {
    printf("Hello, World!\n");
}

fn add(x: i64, y: i64): i64 {
    x + y
}

fn get_answer(): i64 {
    42
}

Function Parameters

Functions can accept multiple parameters with explicit types:

fn calculate_area(width: f64, height: f64): f64 {
    width * height
}

fn format_name(first: str, last: str): str {
    // String concatenation would be here if supported
    first  // For now, just return first name
}

Return Types

Functions must specify their return type:

fn divide(a: i64, b: i64): i64 {
    a / b
}

fn is_positive(x: i64): bool {
    x > 0
}

fn do_nothing(): void {
    // No return value
}

Return Statements

Functions can use explicit return statements or end with an expression:

// Explicit return
fn explicit_return_add(x: i64, y: i64): i64 {
    return x + y;
}

// Expression return (no semicolon on last line)
fn add(x: i64, y: i64): i64 {
    x + y
}

// Mixed approach
fn baz(x: i64): i64 {
    let intermediate = x * 2;
    return intermediate;
}

Functions as First-Class Values

Functions can be stored in variables and passed around:

fn add(x: i64, y: i64): i64 {
    x + y
}

fn multiply(x: i64, y: i64): i64 {
    x * y
}

// Store function in variable
let operation: (i64, i64) -> i64 = add;

// Use the function variable
let result = operation(10, 20);  // 30

// Function as struct field
struct Calculator {
    op: (i64, i64) -> i64;
    name: str;
}

let calc = Calculator {
    op: multiply,
    name: "Multiplier"
};

Higher-Order Functions

Functions can take other functions as parameters:

fn takes_function(func: (i64, i64) -> i64): i64 {
    func(42, 69)
}

fn apply_operation(x: i64, y: i64, op: (i64, i64) -> i64): i64 {
    op(x, y)
}

// Usage
let result1 = takes_function(add);       // 111
let result2 = apply_operation(10, 5, multiply);  // 50

Function Examples from Y Code

Basic Function Usage

declare printf: (str) -> void;

fn baz(x: i64): i64 {
    let intermediate = x * 2;
    return intermediate;
}

fn main(): i64 {
    printf("Foo\n");
    let x = 12;
    let a = baz(x);
    return x + a;
}

Functions Returning Functions

fn foobar(): (i64) -> i64 {
    return \(x) => x;  // Returns a lambda
}

fn create_adder(base: i64): (i64) -> i64 {
    return \(x) => x + base;
}

Functions with Complex Parameters

fn process_struct(data: TestStruct): i64 {
    return data.x;
}

fn takes_array(arr: &[i64]): i64 {
    arr[0]
}

Function Patterns

Factory Functions

fn create_point(x: f64, y: f64): Point {
    Point { x: x, y: y }
}

fn create_default_config(): Config {
    Config {
        debug: false,
        port: 8080,
        timeout: 30
    }
}

Utility Functions

fn max(a: i64, b: i64): i64 {
    if (a > b) { a } else { b }
}

fn clamp(value: i64, min: i64, max: i64): i64 {
    if (value < min) {
        min
    } else if (value > max) {
        max
    } else {
        value
    }
}

Recursive Functions

fn factorial(n: i64): i64 {
    if (n <= 1) {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn fibonacci(n: i64): i64 {
    if (n <= 1) {
        n
    } else {
        fibonacci(n - 1) + fibonacci(n - 2)
    }
}

Function Type Signatures

Function types use the (param_types) -> return_type syntax:

// Function that takes no parameters and returns i64
let getter: () -> i64 = get_answer;

// Function that takes two i64s and returns i64
let binary_op: (i64, i64) -> i64 = add;

// Function that takes a function and returns i64
let higher_order: ((i64) -> i64) -> i64 = some_function;

// Complex function type
let complex: (str, &[i64], (i64) -> bool) -> &[str] = process_data;

Main Function

Every Y program must have a main function:

fn main(): i64 {
    // Program entry point
    // Return 0 for success, non-zero for error
    return 0;
}

// Or with void return
fn main(): void {
    // Program logic here
}

External Function Declarations

You can declare functions implemented externally (like C functions):

declare printf: (str) -> void;
declare malloc: (i64) -> void;
declare strlen: (str) -> i64;

// Usage
fn main(): void {
    printf("Hello from Y!\n");
}

Best Practices

Function Naming

// Good: Clear, descriptive names
fn calculate_total_price(items: &[Item]): f64 { ... }
fn is_valid_email(email: str): bool { ... }
fn format_currency(amount: f64): str { ... }

// Less ideal: Unclear names
fn calc(x: &[Item]): f64 { ... }
fn check(s: str): bool { ... }
fn fmt(n: f64): str { ... }

Function Size

Keep functions focused on a single responsibility:

// Good: Single responsibility
fn validate_age(age: i64): bool {
    age >= 0 && age <= 150
}

fn calculate_tax(amount: f64, rate: f64): f64 {
    amount * rate
}

// Better than one large function handling everything
fn process_order(order: Order): OrderResult {
    validate_order(order);
    calculate_total(order);
    apply_discounts(order);
    finalize_order(order)
}

Type Annotations

Always provide explicit type annotations for function parameters and return types:

// Good: Clear types
fn process_data(input: &[i64], threshold: i64): &[i64] { ... }

// Required: Y needs explicit function signatures
fn calculate(x: i64, y: i64): i64 { x + y }

Error Handling

Design functions to handle edge cases:

fn safe_divide(a: i64, b: i64): i64 {
    if (b == 0) {
        return 0;  // Or handle error appropriately
    } else {
        return a / b;
    }
}

fn get_array_element(arr: &[i64], index: i64): i64 {
    // Bounds checking would go here
    return arr[index];
}