AIScript treats functions as first-class citizens, enabling powerful functional programming patterns through closures and lambdas. These constructs allow you to write more concise, flexible, and expressive code.
A closure is a function that captures and remembers the environment in which it was created. This means it can access variables from its enclosing scope, even after that scope has completed execution.
fn create_counter(start: int) {
let count = start;
fn increment() -> int {
count += 1;
return count;
}
return increment;
}
let counter = create_counter(10);
print(counter()); // 11
print(counter()); // 12
print(counter()); // 13In this example, the increment function forms a closure by capturing the count variable from its outer scope. Each time counter is called, it remembers and updates the same count variable.
Lambdas (also called anonymous functions) are concise function expressions that you can define inline without naming. AIScript provides a simple syntax for lambdas using the pipe | character:
// Basic lambda syntax
let add = |a, b| { return a + b; };
print(add(5, 3)); // 8
// For single expressions, you can omit the braces and return statement
let multiply = |a, b| a * b;
print(multiply(4, 6)); // 24Closures and lambdas shine when used with higher-order functions (functions that take other functions as arguments or return them):
// Map example
let numbers = [1, 2, 3, 4, 5];
let squared = map(numbers, |n| n * n);
print(squared); // [1, 4, 9, 16, 25]
// Filter example
let even_numbers = filter(numbers, |n| n % 2 == 0);
print(even_numbers); // [2, 4]Closures can capture variables in three different ways:
let x = 10;
let add_x = |n| n + x;
print(add_x(5)); // 15
x = 20;
print(add_x(5)); // 25 (reflects updated value of x)When a closure modifies a captured variable, it affects the original:
let counter = 0;
let increment = || { counter += 1; return counter; };
print(increment()); // 1
print(increment()); // 2
print(counter); // 2 (affected by the closure)You can immediately invoke a lambda after defining it:
let result = |x, y| {
let sum = x + y;
let product = x * y;
return sum + product;
}(3, 4);
print(result); // 19 (sum: 7, product: 12)Closures can call themselves recursively:
// Using a named function for recursion
let factorial = |n| {
fn fact(x) -> int {
if x <= 1 {
return 1;
}
return x * fact(x - 1);
}
return fact(n);
};
print(factorial(5)); // 120Closures are ideal for event-driven or asynchronous programming:
fn fetch_data(url: str, on_success, on_error) {
// Simulating an async operation
if url.starts_with("https") {
on_success(f"Data from {url}");
} else {
on_error(f"Invalid URL: {url}");
}
}
fetch_data(
"https://api.example.com",
|data| print("Success:", data),
|error| print("Error:", error)
);:::danger Notice
AIScript current doesn't support fn as type annotation.
fn fetch_data(url: str, on_success: fn(data: str), on_error: fn(error: str)) {}:::
While closures provide powerful abstractions, they have memory implications since they maintain references to their captured environment. Consider these best practices:
- Limit capture scope: Only capture what you need
- Be mindful of memory: Large closures with many captures may impact performance
- Consider alternatives: For simple cases, regular functions might be more efficient
Closures and lambdas are fundamental to many modern programming patterns, from functional programming to asynchronous workflows. By mastering these concepts in AIScript, you'll be able to write more concise, maintainable, and powerful code.