Functions & Lambdas
Chemical supports top-level functions, methods, and powerful lambda expressions.
Function Declarations
Functions are declared with the func keyword.
public func add(a : int, b : int) : int {
return a + b
}
Default Parameters
Functions can have default values for parameters. When a parameter has a default value, it becomes optional when calling the function.
func greet(name : *char = "World") {
printf("Hello, %s!\n", name)
}
greet() // Prints: Hello, World!
greet("Alice") // Prints: Hello, Alice!
Default Parameters in Functions
func give_me_the_default_value(value : int = 832) : int {
return value
}
give_me_the_default_value() // Returns 832
give_me_the_default_value(100) // Returns 100
Default Parameters in Struct Methods
Methods can also have default parameters:
struct Calculator {
func add(&self, a : int, b : int = 10) : int {
return a + b
}
}
var calc = Calculator {}
calc.add(5) // Returns 15 (5 + default 10)
calc.add(5, 20) // Returns 25 (5 + 20)
NOTEDefault parameters must be placed at the end of the parameter list. You cannot have a non-default parameter after a default one.
Function Pointers
A basic function type refers to a pure function pointer. These cannot capture variables from their surrounding scope.
var my_ptr : () => void = () => {
printf("I am a simple function pointer\n");
}
Capturing with std::function
To capture variables from the environment (closures), you must use the std::function type. The capture occurs within the || block.
Only std::function can have capturing lambdas.
import std
func create_counter() : std::function<() => int> {
var count = 0
// Capturing 'count' via ||
var f : std::function<() => int> = |count| () => {
count = count + 1
return count
}
return f
}
Capture Types
Value Capture (|val|)
Copies the variable's value at the time of lambda creation:
var temp = 11
var fn : std::function<() => int> = |temp|() => {
return temp // Uses the copied value
}
Reference Capture (|&var|)
Captures a read-only reference to the original variable:
var temp = 434
var result = take_cap_func(|&temp|() => {
return *temp // Must dereference to access value
})
If you capture anything by reference, and the original dies, accessing it through the reference would result in a memory exception
Mutable Reference Capture (|&mut var|)
Captures a mutable reference, allowing modification:
var temp = 0
take_cap_func(|&mut temp|() => {
*temp = 121 // Modifies the original variable
return 0
})
// temp is now 121
Multiple Captures
Capture multiple variables in a single lambda:
func wrap_cap_lamb(ptr : *mut int, fn : std::function<() => int>) : std::function<() => void> {
return |ptr, fn|() => {
var result = fn()
*ptr = result
}
}
Implicit Conversion
You can assign a capturing lambda directly to a std::function. The compiler provides an implicit constructor that wraps the lambda into a capturing object.
var factor = 2
// Implicitly converted to std::function because of variable capture
var multiplier : std::function<(int) => int> = |factor| (n) => {
return n * factor
}
IMPORTANTIf you attempt to capture variables|x|in a lambda assigned to a raw function pointer(int) => int, the compiler will issue an error.
Capturing Lambdas with Parameters
Capturing lambdas can take parameters just like regular functions:
func take_cap_func(fun : std::function<(a : int) => int>) : int {
return fun(256)
}
// No capture
const arg = take_cap_func(||(a : int) => {
return a * 2
})
// With capture
var x = 100
const arg2 = take_cap_func(|x|(a : int) => {
return a + x
})
Automatic Dereference in Indexing
When a captured reference is used in an index operation, it's automatically dereferenced:
var index = 0
const arg = take_cap_func(|&mut index|(a : int) => {
var arr = [ 8323, 23485 ]
return arr[index] // index is automatically dereferenced
})
Capturing Structs
By Reference
Capture a struct by reference to call methods on it:
struct MyStruct {
var value : int
func double(&self) : int { return value * 2 }
}
var s = MyStruct { value : 92 }
var result = take_cap_func(|&mut s|() => {
return s.double() // Methods work on captured structs
})
By Pointer
Capture a struct pointer to access methods:
var s = new MyStruct { value : 32 }
var result = take_cap_func(|s|() => {
return s.double()
})
destruct s
Nested Member Access
Access nested struct members through captured pointers:
struct Container { var value : MyStruct }
var container = new Container { value : MyStruct { value : 33 } }
var result = take_cap_func(|container|() => {
return container.value.double()
})
Passing Functions
When designing APIs, use std::function if you want to allow users to pass closures. Use raw function pointers only if you want to enforce zero-allocation, non-capturing callbacks (similar to C).
// API that supports closures
func do_something(callback : std::function<() => void>) {
callback()
}
Extension Functions
Extension functions allow you to add new functionality to existing structs, variants, or interfaces without modifying their original definition. They are defined by specifying the receiver type in parentheses before the function name. Extension functions can only be defined on references to structs/variants/unions.
struct Point {
var x : int
var y : int
}
// Extension function for Point
func (p : &Point) distance_to_origin() : float {
return sqrt((p.x * p.x + p.y * p.y) as float)
}
var pt = Point { x : 3, y : 4 }
var d = pt.distance_to_origin() // Calls extension function
Extension Functions on Inherited Structs
Extension functions defined on a base struct work on all derived structs:
struct BasePoint {
var a : int
var b : int
}
func (point : &BasePoint) sum() : int {
return point.a + point.b
}
struct Vertex : BasePoint {
var c : int
}
var v = Vertex {
BasePoint : BasePoint { a : 23, b : 8 },
c : 2
}
v.sum() // Works! Returns 31
Generic Extension Functions on Structs
Extension functions can be generic:
struct MyStruct : SomeInterface {
// ...
}
func <T> (thing : &mut MyStruct) ext_func_gen() : int {
if(T is short) {
return 2 + thing.some_method()
} else if(T is int) {
return 4 + thing.some_method()
} else {
return 8 + thing.some_method()
}
}
var s = MyStruct {}
s.ext_func_gen<short>() // Returns 2 + ...
s.ext_func_gen<int>() // Returns 4 + ...
s.ext_func_gen<long>() // Returns 8 + ...
Generic Extension Functions on Interfaces
Extension functions are also useful for adding generic logic to interfaces:
interface Printable {
func print(&self)
}
// Extension for any type that implements Printable
func <T : Printable> (item : &T) print_twice() {
item.print()
item.print()
}
// Extension function that calls interface methods
interface Summer {
func sum(&self) : int
}
func <T : Summer> (item : &mut T) sum_twice() : int {
return item.sum() * 2
}
These extension functions are generic, if you want to apply non-generic extensions to interfaces, the interface must be
static using the @static annotation, otherwise compiler would complain. In other words, non-generic extension functions can
only be added to static interfaces
Extension Functions on Variants
Extension functions work on variants too:
variant Option<T> {
Some(value : T)
None()
}
func <T> (opt : &Option<T>) is_some() : bool {
return opt is Option.Some
}
Chemical Docs