Functions and Closures

Master Topaz powerful function system. Learn function declarations, arrow functions, higher-order functions, closures, and functional programming patterns in depth.

Functions are first-class citizens in Topaz. You can store them in variables, pass them as parameters to other functions, and return them from functions. 🚀

�� Basic Function Declaration

Standard Function Declaration

// Basic function structure
function greet(name: string) -> string {
    return "Hello, {name}!"
}

// Function without parameters
function getCurrentTime() -> string {
    return "December 20, 2024"
}

// Function without return value (void)
function logMessage(message: string) {
    print("Log: {message}")
}

// Usage examples
let greeting = greet("Topaz Developer")
print(greeting)  // "Hello, Topaz Developer!"

Parameters and Default Values

// Default parameter values
function calculate(a: int, b: int = 10, operation: string = "add") -> int {
    match operation {
        case "add" => return a + b
        case "subtract" => return a - b
        case "multiply" => return a * b
        case _ => return 0
    }
}

// Various calling methods
let result1 = calculate(5)                    // 5 + 10 = 15
let result2 = calculate(5, 3)                 // 5 + 3 = 8
let result3 = calculate(5, 3, "multiply")     // 5 * 3 = 15

// Named parameters
let result4 = calculate(a: 8, operation: "subtract", b: 3)  // 8 - 3 = 5

Variadic Parameters

// Variadic parameters (...rest)
function sum(numbers: ...int) -> int {
    let mut total = 0
    for number in numbers {
        total += number
    }
    return total
}

let total1 = sum(1, 2, 3, 4, 5)        // 15
let total2 = sum(10, 20)               // 30

// Combining array with additional parameters
function calculateStats(label: string, values: ...float) -> string {
    let count = values.length()
    let average = values.sum() / count
    return "{label}: count={count}, average={average}"
}

print(calculateStats("Test Scores", 85.5, 92.0, 78.5, 89.0))

➡️ Arrow Functions

Basic Arrow Functions

// Simple arrow function
let square = (x: int) => x * x
let greet = (name: string) => "Hi, {name}!"

// Arrow function without parameters
let randomNumber = () => Math.random() * 100

// Complex arrow function
let userInfo = (name: string, age: int) => {
    let status = if age >= 18 { "adult" } else { "minor" }
    return {
        name: name,
        age: age,
        status: status
    }
}

// Usage examples
print(square(5))                    // 25
print(greet("TopazDev"))           // "Hi, TopazDev!"
let info = userInfo("John", 25)
print(info)                        // { name: "John", age: 25, status: "adult" }

Array Methods with Arrow Functions

let numbers = [1, 2, 3, 4, 5]

// map transformation
let squares = numbers.map(x => x * x)
print(squares)  // [1, 4, 9, 16, 25]

// filter filtering
let evens = numbers.filter(x => x % 2 == 0)
print(evens)  // [2, 4]

// reduce reduction
let total = numbers.reduce((acc, current) => acc + current, 0)
print(total)   // 15

// Complex chaining
let result = numbers
    .filter(x => x > 2)
    .map(x => x * 3)
    .reduce((a, b) => a + b, 0)
print(result)   // (3 + 4 + 5) * 3 = 36

🎯 Higher-Order Functions

Functions as Parameters

// Higher-order function definition
function applyOperation(array: [int], operation: function(int) -> int) -> [int] {
    let result = []
    for element in array {
        result.push(operation(element))
    }
    return result
}

// Pass functions as parameters
let numbers = [1, 2, 3, 4]
let squareResult = applyOperation(numbers, x => x * x)
let doubleResult = applyOperation(numbers, x => x * 2)

print(squareResult)  // [1, 4, 9, 16]
print(doubleResult)  // [2, 4, 6, 8]

// Conditional processing function
function conditionalProcess(value: int, condition: function(int) -> bool, processor: function(int) -> int) -> int {
    if condition(value) {
        return processor(value)
    }
    return value
}

let result = conditionalProcess(15, x => x > 10, x => x * 2)
print(result)  // 30 (since 15 > 10, so 15 * 2)

Returning Functions

// Function factory
function createMultiplier(factor: int) -> function(int) -> int {
    return function(x: int) -> int {
        return x * factor
    }
}

let doubler = createMultiplier(2)
let tripler = createMultiplier(3)

print(doubler(5))  // 10
print(tripler(4))  // 12

// Configurable validator function
function createRangeValidator(min: int, max: int) -> function(int) -> bool {
    return function(value: int) -> bool {
        return value >= min && value <= max
    }
}

let adultAgeValidator = createRangeValidator(18, 65)
let studentAgeValidator = createRangeValidator(5, 18)

print(adultAgeValidator(25))  // true
print(studentAgeValidator(16))  // true
print(studentAgeValidator(25))  // false

🔒 Closures

Basic Closures

function createCounter(initialValue: int) -> function() -> int {
    let mut count = initialValue  // Captured by closure
    
    return function() -> int {
        count += 1      // Access to outer variable
        return count
    }
}

let counter1 = createCounter(0)
let counter2 = createCounter(100)

print(counter1())  // 1
print(counter1())  // 2
print(counter2())  // 101
print(counter1())  // 3

Complex Closure Patterns

// Stateful closure
function createAccount(initialBalance: float) -> { deposit: function(float) -> float, withdraw: function(float) -> float, checkBalance: function() -> float } {
    let mut balance = initialBalance
    
    return {
        deposit: function(amount: float) -> float {
            balance += amount
            return balance
        },
        withdraw: function(amount: float) -> float {
            if amount <= balance {
                balance -= amount
            }
            return balance
        },
        checkBalance: function() -> float {
            return balance
        }
    }
}

let myAccount = createAccount(1000.0)
print(myAccount.deposit(500.0))     // 1500.0
print(myAccount.withdraw(200.0))    // 1300.0
print(myAccount.checkBalance())     // 1300.0

// Environment-capturing closure
function createConfigManager(defaultConfig: { [string]: any }) -> function(string, any) -> any {
    let mut currentConfig = defaultConfig.copy()
    
    return function(key: string, value: any = null) -> any {
        if value != null {
            currentConfig[key] = value
        }
        return currentConfig[key]
    }
}

let appConfig = createConfigManager({
    theme: "dark",
    language: "English",
    notifications: true
})

print(appConfig("theme"))          // "dark"
appConfig("theme", "light")
print(appConfig("theme"))          // "light"

🔄 Recursive Functions

Basic Recursion

// Factorial calculation
function factorial(n: int) -> int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)
}

print(factorial(5))  // 120

// Fibonacci sequence
function fibonacci(n: int) -> int {
    if n <= 1 {
        return n
    }
    return fibonacci(n - 1) + fibonacci(n - 2)
}

print(fibonacci(8))  // 21

// Array sum (recursive)
function arraySum(array: [int]) -> int {
    if array.length() == 0 {
        return 0
    }
    if array.length() == 1 {
        return array[0]
    }
    return array[0] + arraySum(array.slice(1))
}

print(arraySum([1, 2, 3, 4, 5]))  // 15

Tail Recursion Optimization

// Tail recursive factorial
function tailFactorial(n: int, accumulator: int = 1) -> int {
    if n <= 1 {
        return accumulator
    }
    return tailFactorial(n - 1, accumulator * n)  // Tail call
}

// Tail recursive fibonacci
function tailFibonacci(n: int, a: int = 0, b: int = 1) -> int {
    if n == 0 {
        return a
    }
    return tailFibonacci(n - 1, b, a + b)  // Tail call
}

print(tailFactorial(10))  // 3628800
print(tailFibonacci(10))  // 55

⚡ Functional Programming Patterns

Currying

// Curried function
function add(a: int) -> function(int) -> int {
    return function(b: int) -> int {
        return a + b
    }
}

let addFive = add(5)
print(addFive(3))  // 8

// Multi-parameter currying
function multiplyAndAdd(multiplier: float) -> function(float) -> function(float) -> float {
    return function(addend: float) -> function(float) -> float {
        return function(value: float) -> float {
            return value * multiplier + addend
        }
    }
}

let doubleAndAddTen = multiplyAndAdd(2.0)(10.0)
print(doubleAndAddTen(5.0))  // 20.0 (5 * 2 + 10)

Function Composition

// Function composition utility
function compose<T, U, V>(f: function(U) -> V, g: function(T) -> U) -> function(T) -> V {
    return function(x: T) -> V {
        return f(g(x))
    }
}

let square = (x: int) => x * x
let double = (x: int) => x * 2
let toString = (x: int) => x.toString()

// Compose functions
let squareThenDouble = compose(double, square)
let squareThenDoubleThenString = compose(toString, squareThenDouble)

print(squareThenDouble(3))         // 18 (3² * 2)
print(squareThenDoubleThenString(4))  // "32" (4² * 2 → string)

Memoization

// Memoization decorator
function memoize<T, R>(func: function(T) -> R) -> function(T) -> R {
    let mut cache = {}
    
    return function(input: T) -> R {
        let key = input.toString()
        if key in cache.keys {
            return cache[key]
        }
        
        let result = func(input)
        cache[key] = result
        return result
    }
}

// Optimize slow fibonacci with memoization
let memoFibonacci = memoize(function(n: int) -> int {
    if n <= 1 return n
    return memoFibonacci(n - 1) + memoFibonacci(n - 2)
})

// Now large numbers calculate quickly
print(memoFibonacci(40))  // Calculated quickly

🛡️ Function Optimization and Best Practices

1. Using Pure Functions

// Good: Pure function
function calculateTax(income: float, taxRate: float) -> float {
    return income * taxRate
}

// Avoid: Function with side effects
let mut globalCounter = 0
function badFunction(value: int) -> int {
    globalCounter += 1  // Side effect!
    return value * 2
}

// Good alternative: Return state
function goodFunction(value: int, currentCounter: int) -> { result: int, newCounter: int } {
    return {
        result: value * 2,
        newCounter: currentCounter + 1
    }
}

2. Function Naming and Structure

// Clear function names
function validateUserEmail(email: string) -> bool {
    return email.contains("@") && email.contains(".")
}

function calculateOrderTotal(orderItems: [{ price: float, quantity: int }]) -> float {
    return orderItems
        .map(item => item.price * item.quantity)
        .reduce((total, amount) => total + amount, 0.0)
}

// Single responsibility principle
function parseUserData(rawData: string) -> { name: string, age: int } {
    // Only responsible for parsing
    let parts = rawData.split(",")
    return {
        name: parts[0].trim(),
        age: parseInt(parts[1].trim())
    }
}

function validateUserData(user: { name: string, age: int }) -> bool {
    // Only responsible for validation
    return user.name.length() > 0 && user.age > 0
}

3. Error Handling

// Safe function using Result type
function safeDivide(numerator: float, denominator: float) -> Result<float, string> {
    if denominator == 0.0 {
        return Err("Cannot divide by zero")
    }
    return Ok(numerator / denominator)
}

// Usage
match safeDivide(10.0, 2.0) {
    case Ok(result) => print("Result: {result}")
    case Err(error) => print("Error: {error}")
}

Functions and closures are core features of Topaz. Using them effectively allows you to write reusable and testable code! 🎯