Function
Functions are first-class values in Andy C++. You can store them in variables, pass them as arguments, and call them like any other value.
Named functions
Declare named functions with fn.
fn my_function(input) {
if input == something {
return foo;
}
// do some more stuff
// implicitly return the last expression in the block
bar
}
Function declarations are not hoisted. Define a function before you call it.
foo(); // ERROR: no function called foo exists
fn foo() {
print("Hello, World!");
}
Functions close over their environment.
let x = [];
fn my_function(n) {
// add argument n to x
x.push(n);
// return the sum of x
x.sum()
}
assert_eq(10, my_function(10));
assert_eq(15, my_function(5));
assert_eq([10, 5], x);
Anonymous functions
Anonymous functions let you write inline function values.
// An anonymous function that adds its operands together
let my_function = fn(a, b) { a + b };
assert_eq(my_function(5, 3), 8);
fn foo() {
// whatever
}
let my_variable = foo; // Store the foo-function in a variable
map applies an anonymous function to each element in the list:
let my_list = [x for x in 1..10];
let out = my_list.map(fn(x) => x * 10);
Arrow syntax
Use the fat arrow => for functions that return a single expression.
// Works for anonymous functions
let my_function = fn(a, b) => a + b;
// Also works for named functions
fn foo() => "whatever";
// Note that commas have a very low precedence in this example x is a tuple (function, 3)
let x = fn(y) => y, 3;
// If you want to return a tuple from a function written in this way you must use parentheses
let x = fn(y) => (y, 3);
Function overloading
You can overload functions by declaring multiple fn definitions with the same name and different parameter counts.
fn add(n) { n + 1 };
fn add(x, y) { x + y };
assert_eq(10, add(9));
assert_eq(12, add(8, 4));
Declaring two fn definitions with the same name and the same number of parameters in the same scope is an error:
fn foo(a) { a + 1 }
fn foo(a) { a + 2 } // ERROR: redefinition of 'foo' with 1 parameter
Note: The engine can also overload functions by argument type, and the standard library uses that support in a few places. You cannot write those overloads in user code yet because the language does not let you declare argument types in function signatures.
Function shadowing
A fn declaration in a nested scope shadows only the overload with the same parameter count from outer scopes. Other overloads remain available:
fn foo(a) { "outer-one" }
fn foo(a, b) { "outer-two" }
{
fn foo(a) { "inner-one" }
foo("x"); // "inner-one": inner 1-arg shadows outer 1-arg
foo("x", "y"); // "outer-two": outer 2-arg still reachable
}
foo("x"); // "outer-one": shadow is gone after the block
A let binding with a function value replaces all previous bindings with the same name. It does not participate in function overloading:
fn foo(a) { "one" }
fn foo(a, b) { "two" }
let foo = fn(a) => "let";
foo("x"); // "let": both fn overloads are shadowed
A non-function let binding shadows the name for value access, but function calls still resolve to the underlying function:
let len = 300;
len; // 300: the value
len("test"); // 4: calls the stdlib function, skipping the non-function binding
"test".len; // 4: method call also resolves to the function
Method call syntax
In Andy C++, you can call a function as a method on its first argument. You get method-style syntax without defining member functions.
fn add(x, y) {
return x + y;
}
// Normal function call
print(add(3, 5));
// Method-like syntax
print(3.add(5));
Both forms do the same thing.
Implicit call
You can also omit () when you call a 0-ary function.
let input = "some text\nsome more text";
let lines = input.lines;